diff --git a/.github/workflows/checks.yaml b/.github/workflows/checks.yaml index 9961d031..c2785110 100644 --- a/.github/workflows/checks.yaml +++ b/.github/workflows/checks.yaml @@ -138,21 +138,21 @@ jobs: --client-id=opentdf-sdk \ --client-secret=secret \ --platform-endpoint=localhost:8080 \ - -i -h \ + -h\ encrypt --kas-url=localhost:8080 --mime-type=text/plain --attr https://example.com/attr/attr1/value/value1 --autoconfigure=false -f data -m 'here is some metadata' > test.tdf java -jar target/cmdline.jar \ --client-id=opentdf-sdk \ --client-secret=secret \ --platform-endpoint=localhost:8080 \ - -i -h \ + -h\ decrypt -f test.tdf > decrypted java -jar target/cmdline.jar \ --client-id=opentdf-sdk \ --client-secret=secret \ --platform-endpoint=localhost:8080 \ - -i -h \ + -h\ metadata -f test.tdf > metadata if ! diff -q data decrypted; then @@ -174,14 +174,14 @@ jobs: --client-id=opentdf-sdk \ --client-secret=secret \ --platform-endpoint=localhost:8080 \ - -i -h \ + -h\ encryptnano --kas-url=http://localhost:8080 --attr https://example.com/attr/attr1/value/value1 -f data -m 'here is some metadata' > nano.ntdf java -jar target/cmdline.jar \ --client-id=opentdf-sdk \ --client-secret=secret \ --platform-endpoint=localhost:8080 \ - -i -h \ + -h\ decryptnano -f nano.ntdf > decrypted if ! diff -q data decrypted; then @@ -216,21 +216,21 @@ jobs: --client-id=opentdf-sdk \ --client-secret=secret \ --platform-endpoint=localhost:8080 \ - -i -h \ + -h\ encrypt --kas-url=localhost:8080,localhost:8282 -f data -m 'here is some metadata' > test.tdf java -jar target/cmdline.jar \ --client-id=opentdf-sdk \ --client-secret=secret \ --platform-endpoint=localhost:8080 \ - -i -h \ + -h\ decrypt -f test.tdf > decrypted java -jar target/cmdline.jar \ --client-id=opentdf-sdk \ --client-secret=secret \ --platform-endpoint=localhost:8080 \ - -i -h \ + -h\ metadata -f test.tdf > metadata if ! diff -q data decrypted; then diff --git a/.gitignore b/.gitignore index 476c7749..35853a81 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ -protocol/src/main/protogen +sdk/src/main/protogen /.idea/ target/ .vscode/ .DS_Store +sdk/sample.tdf \ No newline at end of file diff --git a/buf.gen.yaml b/buf.gen.yaml index e829ee09..797abc12 100644 --- a/buf.gen.yaml +++ b/buf.gen.yaml @@ -9,6 +9,6 @@ managed: - buf.build/grpc-ecosystem/grpc-gateway plugins: - plugin: buf.build/protocolbuffers/java:v25.3 - out: protocol/src/main/protogen + out: sdk/src/main/protogen - plugin: buf.build/grpc/java:v1.61.1 - out: protocol/src/main/protogen + out: sdk/src/main/protogen diff --git a/cmdline/pom.xml b/cmdline/pom.xml index 65b0d13e..8d7a128f 100644 --- a/cmdline/pom.xml +++ b/cmdline/pom.xml @@ -1,12 +1,12 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 io.opentdf.platform sdk-pom - 0.6.2-SNAPSHOT + ${revision} cmdline @@ -18,27 +18,52 @@ org.apache.maven.plugins - maven-assembly-plugin - 3.7.1 + maven-shade-plugin + 3.5.3 package - single + shade + + + false + cmdline + + + io.opentdf.platform.TDF + + ${version} + io.opentdf.platform.TDF + + + + + + + *:* + + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + + + + + + + + + org.apache.maven.plugins + maven-deploy-plugin + 3.1.1 - - jar-with-dependencies - - cmdline - false - - - io.opentdf.platform.TDF - - + true @@ -52,7 +77,7 @@ io.opentdf.platform sdk - 0.6.2-SNAPSHOT + ${project.version} \ No newline at end of file diff --git a/cmdline/src/main/java/io/opentdf/platform/Command.java b/cmdline/src/main/java/io/opentdf/platform/Command.java index ca784283..71770cd2 100644 --- a/cmdline/src/main/java/io/opentdf/platform/Command.java +++ b/cmdline/src/main/java/io/opentdf/platform/Command.java @@ -39,31 +39,32 @@ @CommandLine.Command(name = "tdf") class Command { - @Option(names = {"--client-secret"}, required = true) + @Option(names = { "--client-secret" }, required = true) private String clientSecret; - @Option(names = {"-h", "--plaintext"}, defaultValue = "false") + @Option(names = { "-h", "--plaintext" }, defaultValue = "false") private boolean plaintext; - @Option(names = {"-i", "--insecure"}, defaultValue = "false") + @Option(names = { "-i", "--insecure" }, defaultValue = "false") private boolean insecure; - @Option(names = {"--client-id"}, required = true) + @Option(names = { "--client-id" }, required = true) private String clientId; - @Option(names = {"-p", "--platform-endpoint"}, required = true) + @Option(names = { "-p", "--platform-endpoint" }, required = true) private String platformEndpoint; @CommandLine.Command(name = "encrypt") void encrypt( - @Option(names = {"-f", "--file"}, defaultValue = Option.NULL_VALUE) Optional file, - @Option(names = {"-k", "--kas-url"}, required = true, split = ",") List kas, - @Option(names = {"-m", "--metadata"}, defaultValue = Option.NULL_VALUE) Optional metadata, + @Option(names = { "-f", "--file" }, defaultValue = Option.NULL_VALUE) Optional file, + @Option(names = { "-k", "--kas-url" }, required = true, split = ",") List kas, + @Option(names = { "-m", "--metadata" }, defaultValue = Option.NULL_VALUE) Optional metadata, // cant split on optional parameters - @Option(names = {"-a", "--attr"}, defaultValue = Option.NULL_VALUE) Optional attributes, - @Option(names = {"-c", "--autoconfigure"}, defaultValue = Option.NULL_VALUE) Optional autoconfigure, - @Option(names = {"--mime-type"}, defaultValue = Option.NULL_VALUE) Optional mimeType) throws - IOException, JOSEException, AutoConfigureException, InterruptedException, ExecutionException { + @Option(names = { "-a", "--attr" }, defaultValue = Option.NULL_VALUE) Optional attributes, + @Option(names = { "-c", + "--autoconfigure" }, defaultValue = Option.NULL_VALUE) Optional autoconfigure, + @Option(names = { "--mime-type" }, defaultValue = Option.NULL_VALUE) Optional mimeType) + throws IOException, JOSEException, AutoConfigureException, InterruptedException, ExecutionException { var sdk = buildSDK(); var kasInfos = kas.stream().map(k -> { @@ -71,57 +72,55 @@ void encrypt( ki.URL = k; return ki; }).toArray(Config.KASInfo[]::new); - List> configs = new ArrayList<>(); configs.add(Config.withKasInformation(kasInfos)); metadata.map(Config::withMetaData).ifPresent(configs::add); autoconfigure.map(Config::withAutoconfigure).ifPresent(configs::add); mimeType.map(Config::withMimeType).ifPresent(configs::add); - if (attributes.isPresent()){ + if (attributes.isPresent()) { configs.add(Config.withDataAttributes(attributes.get().split(","))); } var tdfConfig = Config.newTDFConfig(configs.toArray(Consumer[]::new)); try (var in = file.isEmpty() ? new BufferedInputStream(System.in) : new FileInputStream(file.get())) { try (var out = new BufferedOutputStream(System.out)) { - new TDF().createTDF(in, out, tdfConfig, - sdk.getServices().kas(), - sdk.getServices().attributes() - ); + new TDF().createTDF(in, out, tdfConfig, + sdk.getServices().kas(), + sdk.getServices().attributes()); } } } private SDK buildSDK() { SDKBuilder builder = new SDKBuilder(); - if (insecure){ + if (insecure) { SSLFactory sslFactory = SSLFactory.builder() - .withUnsafeTrustMaterial() // Trust all certificates - .build(); + .withUnsafeTrustMaterial() // Trust all certificates + .build(); builder.sslFactory(sslFactory); } - - return builder.platformEndpoint(platformEndpoint) - .clientSecret(clientId, clientSecret) - .useInsecurePlaintextConnection(plaintext) + + return builder.platformEndpoint(platformEndpoint) + .clientSecret(clientId, clientSecret).useInsecurePlaintextConnection(plaintext) .build(); } @CommandLine.Command(name = "decrypt") - void decrypt(@Option(names = {"-f", "--file"}, required = true) Path tdfPath) throws IOException, + void decrypt(@Option(names = { "-f", "--file" }, required = true) Path tdfPath) throws IOException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, TDF.FailedToCreateGMAC, JOSEException, ParseException, NoSuchAlgorithmException, DecoderException { var sdk = buildSDK(); try (var in = FileChannel.open(tdfPath, StandardOpenOption.READ)) { try (var stdout = new BufferedOutputStream(System.out)) { - var reader = new TDF().loadTDF(in, sdk.getServices().kas()); - reader.readPayload(stdout); - } + var reader = new TDF().loadTDF(in, sdk.getServices().kas()); + reader.readPayload(stdout); + } } } + @CommandLine.Command(name = "metadata") - void readMetadata(@Option(names = {"-f", "--file"}, required = true) Path tdfPath) throws IOException, + void readMetadata(@Option(names = { "-f", "--file" }, required = true) Path tdfPath) throws IOException, TDF.FailedToCreateGMAC, JOSEException, NoSuchAlgorithmException, ParseException, DecoderException { var sdk = buildSDK(); @@ -135,10 +134,11 @@ void readMetadata(@Option(names = {"-f", "--file"}, required = true) Path tdfPat @CommandLine.Command(name = "encryptnano") void createNanoTDF( - @Option(names = {"-f", "--file"}, defaultValue = Option.NULL_VALUE) Optional file, - @Option(names = {"-k", "--kas-url"}, required = true) List kas, - @Option(names = {"-m", "--metadata"}, defaultValue = Option.NULL_VALUE) Optional metadata, - @Option(names = {"-a", "--attr"}, defaultValue = Option.NULL_VALUE) Optional attributes) throws Exception { + @Option(names = { "-f", "--file" }, defaultValue = Option.NULL_VALUE) Optional file, + @Option(names = { "-k", "--kas-url" }, required = true) List kas, + @Option(names = { "-m", "--metadata" }, defaultValue = Option.NULL_VALUE) Optional metadata, + @Option(names = { "-a", "--attr" }, defaultValue = Option.NULL_VALUE) Optional attributes) + throws Exception { var sdk = buildSDK(); var kasInfos = kas.stream().map(k -> { @@ -163,7 +163,7 @@ void createNanoTDF( } @CommandLine.Command(name = "decryptnano") - void readNanoTDF(@Option(names = {"-f", "--file"}, required = true) Path nanoTDFPath) throws Exception { + void readNanoTDF(@Option(names = { "-f", "--file" }, required = true) Path nanoTDFPath) throws Exception { var sdk = buildSDK(); try (var in = FileChannel.open(nanoTDFPath, StandardOpenOption.READ)) { try (var stdout = new BufferedOutputStream(System.out)) { diff --git a/pom.xml b/pom.xml index 1b3475e0..317d9f21 100644 --- a/pom.xml +++ b/pom.xml @@ -1,26 +1,27 @@ - + 4.0.0 io.opentdf.platform sdk-pom - 0.6.2-SNAPSHOT + ${revision} sdk-pom pom UTF-8 - + 11 11 2.20.0 1.63.0 3.25.3 8.3.5 + 0.6.2-SNAPSHOT - protocol sdk cmdline @@ -114,14 +115,17 @@ - + - + maven-clean-plugin 3.1.0 - + maven-resources-plugin 3.0.2 @@ -146,7 +150,8 @@ maven-deploy-plugin 2.8.2 - + maven-site-plugin 3.7.1 @@ -162,10 +167,10 @@ false - - - - + + + + @@ -187,12 +192,12 @@ ${maven.compiler.target} WARN - - - - - - + + + + + + @@ -234,5 +239,4 @@ - - + \ No newline at end of file diff --git a/protocol/pom.xml b/protocol/pom.xml deleted file mode 100644 index 143caf39..00000000 --- a/protocol/pom.xml +++ /dev/null @@ -1,105 +0,0 @@ - - - - 4.0.0 - io.opentdf.platform - protocol - protocol - 0.6.2-SNAPSHOT - - sdk-pom - io.opentdf.platform - 0.6.2-SNAPSHOT - - jar - - - com.google.protobuf - protobuf-java - ${protobuf.version} - - - javax.annotation - javax.annotation-api - 1.3.2 - - - build.buf - protovalidate - 0.1.9 - - - io.grpc - grpc-protobuf - - - io.grpc - grpc-stub - - - - - - org.apache.maven.plugins - maven-antrun-plugin - 3.1.0 - - - generateSources - generate-sources - - - - - - - - - - - - - - - - - - - - - - - - - - - - - run - - - - - - org.codehaus.mojo - build-helper-maven-plugin - 3.5.0 - - - add-source - generate-sources - - add-source - - - - src/main/protogen - - - - - - - - diff --git a/release-please.json b/release-please.json index 1df29bb8..aaa7732d 100644 --- a/release-please.json +++ b/release-please.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", - "release-type": "java-yoshi-mono-repo", + "release-type": "maven", "bump-minor-pre-major": true, "bump-patch-for-minor-pre-major": true, "separate-pull-requests": false, @@ -9,4 +9,4 @@ "packages": { ".": {} } -} +} \ No newline at end of file diff --git a/protocol/buf.lock b/sdk/buf.lock similarity index 100% rename from protocol/buf.lock rename to sdk/buf.lock diff --git a/protocol/buf.yaml b/sdk/buf.yaml similarity index 100% rename from protocol/buf.yaml rename to sdk/buf.yaml diff --git a/sdk/pom.xml b/sdk/pom.xml index 05c3f19b..20a48594 100644 --- a/sdk/pom.xml +++ b/sdk/pom.xml @@ -1,22 +1,18 @@ - + 4.0.0 sdk sdk sdk-pom io.opentdf.platform - 0.6.2-SNAPSHOT + ${revision} jar - - io.opentdf.platform - protocol - 0.6.2-SNAPSHOT - org.slf4j slf4j-api @@ -28,7 +24,7 @@ test - org.apache.logging.log4j + org.apache.logging.log4j log4j-core test @@ -135,5 +131,75 @@ org.mockito mockito-junit-jupiter + + build.buf + protovalidate + 0.1.9 + - + + + + org.apache.maven.plugins + maven-antrun-plugin + 3.1.0 + + + generateSources + generate-sources + + + + + + + + + + + + + + + + + + + + + + + + + + + + + run + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 3.5.0 + + + add-source + generate-sources + + add-source + + + + src/main/protogen + + + + + + + + \ No newline at end of file diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/AssertionConfig.java b/sdk/src/main/java/io/opentdf/platform/sdk/AssertionConfig.java index 6a8c229c..510a008c 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/AssertionConfig.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/AssertionConfig.java @@ -1,10 +1,8 @@ package io.opentdf.platform.sdk; -import com.nimbusds.jose.JWSSigner; import java.util.Objects; - public class AssertionConfig { public enum Type { @@ -70,7 +68,7 @@ public AssertionKey(AssertionKeyAlg alg, Object key) { this.key = key; } - public boolean isDefined() { + public boolean isDefined() { return alg != AssertionKeyAlg.NotDefined; } } @@ -82,10 +80,13 @@ static public class Statement { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; Statement statement = (Statement) o; - return Objects.equals(format, statement.format) && Objects.equals(schema, statement.schema) && Objects.equals(value, statement.value); + return Objects.equals(format, statement.format) && Objects.equals(schema, statement.schema) + && Objects.equals(value, statement.value); } @Override diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/Autoconfigure.java b/sdk/src/main/java/io/opentdf/platform/sdk/Autoconfigure.java index 29b7c4d5..5e9eb7da 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/Autoconfigure.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/Autoconfigure.java @@ -45,7 +45,6 @@ class RuleType { public static final String EMPTY_TERM = "DEFAULT"; } - public class Autoconfigure { public static Logger logger = LoggerFactory.getLogger(Autoconfigure.class); @@ -73,7 +72,7 @@ public boolean equals(Object obj) { return false; } KeySplitStep ss = (KeySplitStep) obj; - if ((this.kas.equals(ss.kas)) && (this.splitID.equals(ss.splitID))){ + if ((this.kas.equals(ss.kas)) && (this.splitID.equals(ss.splitID))) { return true; } return false; @@ -185,7 +184,7 @@ public boolean equals(Object obj) { return false; } AttributeValueFQN afqn = (AttributeValueFQN) obj; - if (this.key.equals(afqn.key)){ + if (this.key.equals(afqn.key)) { return true; } return false; @@ -264,8 +263,8 @@ public Granter(List policy) { this.policy = policy; } - public Map getGrants() { - return new HashMap(grants); + public Map getGrants() { + return new HashMap(grants); } public List getPolicy() { @@ -273,8 +272,7 @@ public List getPolicy() { } public void addGrant(AttributeValueFQN fqn, String kas, Attribute attr) { - grants.computeIfAbsent(fqn.key, k -> new KeyAccessGrant(attr, new ArrayList<>())) - .kases.add(kas); + grants.computeIfAbsent(fqn.key, k -> new KeyAccessGrant(attr, new ArrayList<>())).kases.add(kas); } public void addAllGrants(AttributeValueFQN fqn, List gs, Attribute attr) { @@ -293,8 +291,8 @@ public KeyAccessGrant byAttribute(AttributeValueFQN fqn) { return grants.get(fqn.key); } - - public List plan(List defaultKas, Supplier genSplitID) throws AutoConfigureException { + public List plan(List defaultKas, Supplier genSplitID) + throws AutoConfigureException { AttributeBooleanExpression b = constructAttributeBoolean(); BooleanKeyExpression k = insertKeysForAttribute(b); if (k == null) { @@ -330,35 +328,35 @@ public List plan(List defaultKas, Supplier genSpli BooleanKeyExpression insertKeysForAttribute(AttributeBooleanExpression e) throws AutoConfigureException { List kcs = new ArrayList<>(e.must.size()); - + for (SingleAttributeClause clause : e.must) { List kcv = new ArrayList<>(clause.values.size()); - + for (AttributeValueFQN term : clause.values) { KeyAccessGrant grant = byAttribute(term); if (grant == null) { throw new AutoConfigureException(String.format("no definition or grant found for [%s]", term)); } - + List kases = grant.kases; if (kases.isEmpty()) { kases = List.of(RuleType.EMPTY_TERM); } - + for (String kas : kases) { kcv.add(new PublicKeyInfo(kas)); } } - + String op = ruleToOperator(clause.def.getRule()); if (op == RuleType.UNSPECIFIED) { logger.warn("Unknown attribute rule type: " + clause); } - + KeyClause kc = new KeyClause(op, kcv); kcs.add(kc); } - + return new BooleanKeyExpression(kcs); } @@ -371,7 +369,8 @@ AttributeBooleanExpression constructAttributeBoolean() throws AutoConfigureExcep if (clause != null) { clause.values.add(aP); } else if (byAttribute(aP) != null) { - var x = new SingleAttributeClause(byAttribute(aP).attr, new ArrayList(Arrays.asList(aP))); + var x = new SingleAttributeClause(byAttribute(aP).attr, + new ArrayList(Arrays.asList(aP))); prefixes.put(a.getKey(), x); sortedPrefixes.add(a.getKey()); } @@ -384,34 +383,33 @@ AttributeBooleanExpression constructAttributeBoolean() throws AutoConfigureExcep return new AttributeBooleanExpression(must); } - - static class AttributeMapping { private Map dict; - + public AttributeMapping() { this.dict = new HashMap<>(); } - + public void put(Attribute ad) throws AutoConfigureException { if (this.dict == null) { this.dict = new HashMap<>(); } - + AttributeNameFQN prefix = new AttributeNameFQN(ad.getFqn()); - + if (this.dict.containsKey(prefix)) { throw new AutoConfigureException("Attribute prefix already found: [" + prefix.toString() + "]"); } - + this.dict.put(prefix, ad); } - + public Attribute get(AttributeNameFQN prefix) throws AutoConfigureException { Attribute ad = this.dict.get(prefix); if (ad == null) { - throw new AutoConfigureException("Unknown attribute type: [" + prefix.toString() + "], not in [" + this.dict.keySet().toString() + "]"); + throw new AutoConfigureException("Unknown attribute type: [" + prefix.toString() + "], not in [" + + this.dict.keySet().toString() + "]"); } return ad; } @@ -422,7 +420,7 @@ static class SingleAttributeClause { private Attribute def; private List values; - + public SingleAttributeClause(Attribute def, List values) { this.def = def; this.values = values; @@ -432,24 +430,24 @@ public SingleAttributeClause(Attribute def, List values) { class AttributeBooleanExpression { private List must; - + public AttributeBooleanExpression(List must) { this.must = must; } - + @Override public String toString() { if (must == null || must.isEmpty()) { return "∅"; } - + StringBuilder sb = new StringBuilder(); for (int i = 0; i < must.size(); i++) { SingleAttributeClause clause = must.get(i); if (i > 0) { sb.append("&"); } - + List values = clause.values; if (values == null || values.isEmpty()) { sb.append(clause.def.getFqn()); @@ -458,7 +456,7 @@ public String toString() { } else { sb.append(clause.def.getFqn()); sb.append("/value/{"); - + StringJoiner joiner = new StringJoiner(","); for (AttributeValueFQN v : values) { joiner.add(v.value()); @@ -469,20 +467,20 @@ public String toString() { } return sb.toString(); } - + } public class PublicKeyInfo { private String kas; - + public PublicKeyInfo(String kas) { this.kas = kas; } - + public String getKas() { return kas; } - + public void setKas(String kas) { this.kas = kas; } @@ -491,12 +489,12 @@ public void setKas(String kas) { public class KeyClause { private String operator; private List values; - + public KeyClause(String operator, List values) { this.operator = operator; this.values = values; } - + @Override public String toString() { if (values.size() == 1 && values.get(0).getKas().equals(RuleType.EMPTY_TERM)) { @@ -505,14 +503,14 @@ public String toString() { if (values.size() == 1) { return "(" + values.get(0).getKas() + ")"; } - + StringBuilder sb = new StringBuilder(); sb.append("("); String op = "⋀"; if (operator.equals(RuleType.ANY_OF)) { op = "⋁"; } - + for (int i = 0; i < values.size(); i++) { if (i > 0) { sb.append(op); @@ -520,18 +518,18 @@ public String toString() { sb.append(values.get(i).getKas()); } sb.append(")"); - + return sb.toString(); } } public class BooleanKeyExpression { private List values; - + public BooleanKeyExpression(List values) { this.values = values; } - + @Override public String toString() { StringBuilder sb = new StringBuilder(); @@ -576,7 +574,7 @@ public BooleanKeyExpression reduce() { if (conjunction.isEmpty()) { return new BooleanKeyExpression(new ArrayList<>()); } - + List newValues = new ArrayList<>(); for (List d : conjunction) { List pki = new ArrayList<>(); @@ -591,7 +589,7 @@ public BooleanKeyExpression reduce() { public Disjunction sortedNoDupes(List l) { Set set = new HashSet<>(); Disjunction list = new Disjunction(); - + for (PublicKeyInfo e : l) { String kas = e.getKas(); if (!kas.equals(RuleType.EMPTY_TERM) && !set.contains(kas)) { @@ -599,7 +597,7 @@ public Disjunction sortedNoDupes(List l) { list.add(kas); } } - + Collections.sort(list); return list; } @@ -621,7 +619,7 @@ public boolean less(Disjunction r) { } return this.size() < r.size(); } - + public boolean equals(Object obj) { if (this == obj) { return true; @@ -675,23 +673,25 @@ public static String ruleToOperator(AttributeRuleTypeEnum e) { } // Gets a list of directory of KAS grants for a list of attribute FQNs - public static Granter newGranterFromService(AttributesServiceFutureStub as, KASKeyCache keyCache, AttributeValueFQN... fqns) throws AutoConfigureException, InterruptedException, ExecutionException { + public static Granter newGranterFromService(AttributesServiceFutureStub as, KASKeyCache keyCache, + AttributeValueFQN... fqns) throws AutoConfigureException, InterruptedException, ExecutionException { String[] fqnsStr = new String[fqns.length]; for (int i = 0; i < fqns.length; i++) { fqnsStr[i] = fqns[i].toString(); } GetAttributeValuesByFqnsRequest request = GetAttributeValuesByFqnsRequest.newBuilder() - .addAllFqns(Arrays.asList(fqnsStr)) - .setWithValue(AttributeValueSelector.newBuilder().setWithKeyAccessGrants(true).build()) - .build(); + .addAllFqns(Arrays.asList(fqnsStr)) + .setWithValue(AttributeValueSelector.newBuilder().setWithKeyAccessGrants(true).build()) + .build(); GetAttributeValuesByFqnsResponse av; av = as.getAttributeValuesByFqns(request).get(); Granter grants = new Granter(Arrays.asList(fqns)); - for (Map.Entry entry : av.getFqnAttributeValuesMap().entrySet()) { + for (Map.Entry entry : av.getFqnAttributeValuesMap() + .entrySet()) { String fqnstr = entry.getKey(); AttributeAndValue pair = entry.getValue(); @@ -708,10 +708,10 @@ public static Granter newGranterFromService(AttributesServiceFutureStub as, KASK grants.addAllGrants(fqn, v.getGrantsList(), def); storeKeysToCache(v.getGrantsList(), keyCache); } else { - if (def != null) { - grants.addAllGrants(fqn, def.getGrantsList(), def); - storeKeysToCache(def.getGrantsList(), keyCache); - } + if (def != null) { + grants.addAllGrants(fqn, def.getGrantsList(), def); + storeKeysToCache(def.getGrantsList(), keyCache); + } } } diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/Config.java b/sdk/src/main/java/io/opentdf/platform/sdk/Config.java index 591dbe4d..a8766820 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/Config.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/Config.java @@ -67,7 +67,6 @@ public String toString() { } } - public static class AssertionVerificationKeys { public AssertionConfig.AssertionKey defaultKey; public Map keys = new HashMap<>(); @@ -100,7 +99,8 @@ public static TDFReaderConfig newTDFReaderConfig(Consumer... op return config; } - public static Consumer withAssertionVerificationKeys(AssertionVerificationKeys assertionVerificationKeys) { + public static Consumer withAssertionVerificationKeys( + AssertionVerificationKeys assertionVerificationKeys) { return (TDFReaderConfig config) -> config.assertionVerificationKeys = assertionVerificationKeys; } @@ -147,7 +147,7 @@ public static TDFConfig newTDFConfig(Consumer... options) { public static Consumer withDataAttributes(String... attributes) throws AutoConfigureException { List attrValFqns = new ArrayList(); - for (String a : attributes){ + for (String a : attributes) { Autoconfigure.AttributeValueFQN attrValFqn = new Autoconfigure.AttributeValueFQN(a); attrValFqns.add(attrValFqn); } @@ -159,7 +159,7 @@ public static Consumer withDataAttributes(String... attributes) throw public static Consumer withDataAttributeValues(String... attributes) throws AutoConfigureException { List attrValFqns = new ArrayList(); - for (String a : attributes){ + for (String a : attributes) { Autoconfigure.AttributeValueFQN attrValFqn = new Autoconfigure.AttributeValueFQN(a); attrValFqns.add(attrValFqn); } @@ -169,8 +169,10 @@ public static Consumer withDataAttributeValues(String... attributes) }; } - // WithDataAttributeValues appends the given data attributes to the bound policy. - // Unlike `WithDataAttributes`, this will not trigger an attribute definition lookup + // WithDataAttributeValues appends the given data attributes to the bound + // policy. + // Unlike `WithDataAttributes`, this will not trigger an attribute definition + // lookup // during autoconfigure. That is, to use autoconfigure in an 'offline' context, // you must first store the relevant attribute information locally and load // it to the `CreateTDF` method with this option. @@ -222,9 +224,9 @@ public static Consumer withAutoconfigure(boolean enable) { }; } -// public static Consumer withDisableEncryption() { -// return (TDFConfig config) -> config.enableEncryption = false; -// } + // public static Consumer withDisableEncryption() { + // return (TDFConfig config) -> config.enableEncryption = false; + // } public static Consumer withMimeType(String mimeType) { return (TDFConfig config) -> config.mimeType = mimeType; diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java index 3cd9da3a..4732865b 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java @@ -30,7 +30,7 @@ import static java.lang.String.format; -public class KASClient implements SDK.KAS, AutoCloseable { +public class KASClient implements SDK.KAS { private final Function channelFactory; private final RSASSASigner signer; @@ -41,10 +41,12 @@ public class KASClient implements SDK.KAS, AutoCloseable { /*** * A client that communicates with KAS - * @param channelFactory A function that produces channels that can be used to communicate + * + * @param channelFactory A function that produces channels that can be used to + * communicate * @param dpopKey */ - public KASClient(Function channelFactory, RSAKey dpopKey) { + public KASClient(Function channelFactory, RSAKey dpopKey) { this.channelFactory = channelFactory; try { this.signer = new RSASSASigner(dpopKey); @@ -61,7 +63,8 @@ public KASClient(Function channelFactory, RSAKey dpopKe @Override public KASInfo getECPublicKey(Config.KASInfo kasInfo, NanoTDFType.ECCurve curve) { var r = getStub(kasInfo.URL) - .publicKey(PublicKeyRequest.newBuilder().setAlgorithm(String.format("ec:%s", curve.toString())).build()); + .publicKey( + PublicKeyRequest.newBuilder().setAlgorithm(String.format("ec:%s", curve.toString())).build()); var k2 = kasInfo.clone(); k2.KID = r.getKid(); k2.PublicKey = r.getPublicKey(); @@ -75,7 +78,7 @@ public Config.KASInfo getPublicKey(Config.KASInfo kasInfo) { return cachedValue; } PublicKeyResponse resp = getStub(kasInfo.URL).publicKey(PublicKeyRequest.getDefaultInstance()); - + var kiCopy = new Config.KASInfo(); kiCopy.KID = resp.getKid(); kiCopy.PublicKey = resp.getPublicKey(); @@ -87,7 +90,7 @@ public Config.KASInfo getPublicKey(Config.KASInfo kasInfo) { } @Override - public KASKeyCache getKeyCache(){ + public KASKeyCache getKeyCache() { return this.kasKeyCache; } @@ -122,7 +125,7 @@ private String normalizeAddress(String urlString) { public synchronized void close() { var entries = new ArrayList<>(stubs.values()); stubs.clear(); - for (var entry: entries) { + for (var entry : entries) { entry.channel.shutdownNow(); } } @@ -179,7 +182,7 @@ public byte[] unwrap(Manifest.KeyAccess keyAccess, String policy) { return decryptor.decrypt(wrappedKey); } - public byte[] unwrapNanoTDF(NanoTDFType.ECCurve curve, String header, String kasURL) { + public byte[] unwrapNanoTDF(NanoTDFType.ECCurve curve, String header, String kasURL) { ECKeyPair keyPair = new ECKeyPair(curve.toString(), ECKeyPair.ECAlgorithm.ECDH); NanoTDFKeyAccess keyAccess = new NanoTDFKeyAccess(); @@ -190,7 +193,7 @@ public byte[] unwrapNanoTDF(NanoTDFType.ECCurve curve, String header, String kas NanoTDFRewrapRequestBody body = new NanoTDFRewrapRequestBody(); body.algorithm = String.format("ec:%s", curve.toString()); - body.clientPublicKey = keyPair.publicKeyInPEMFormat(); + body.clientPublicKey = keyPair.publicKeyInPEMFormat(); body.keyAccess = keyAccess; var requestBody = gson.toJson(body); @@ -236,9 +239,11 @@ public byte[] unwrapNanoTDF(NanoTDFType.ECCurve curve, String header, String kas } private final HashMap stubs = new HashMap<>(); + private static class CacheEntry { final ManagedChannel channel; final AccessServiceGrpc.AccessServiceBlockingStub stub; + private CacheEntry(ManagedChannel channel, AccessServiceGrpc.AccessServiceBlockingStub stub) { this.channel = channel; this.stub = stub; @@ -257,4 +262,3 @@ synchronized AccessServiceGrpc.AccessServiceBlockingStub getStub(String url) { return stubs.get(realAddress).stub; } } - diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/Manifest.java b/sdk/src/main/java/io/opentdf/platform/sdk/Manifest.java index 27837fbd..831274e2 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/Manifest.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/Manifest.java @@ -21,7 +21,6 @@ import java.security.interfaces.RSAPublicKey; import java.text.ParseException; import java.util.ArrayList; -import java.util.Base64; import java.util.List; import java.util.Objects; @@ -32,10 +31,13 @@ public class Manifest { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + 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); + return Objects.equals(encryptionInformation, manifest.encryptionInformation) + && Objects.equals(payload, manifest.payload) && Objects.equals(assertions, manifest.assertions); } @Override @@ -45,7 +47,8 @@ public int hashCode() { private static class PolicyBindingSerializer implements JsonDeserializer, JsonSerializer { @Override - public Object deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + 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()) { @@ -60,6 +63,7 @@ public JsonElement serialize(Object src, Type typeOfSrc, JsonSerializationContex return context.serialize(src, typeOfSrc); } } + static public class Segment { public String hash; public long segmentSize; @@ -67,10 +71,13 @@ static public class Segment { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + 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); + return segmentSize == segment.segmentSize && encryptedSegmentSize == segment.encryptedSegmentSize + && Objects.equals(hash, segment.hash); } @Override @@ -87,8 +94,10 @@ static public class RootSignature { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + 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); } @@ -108,26 +117,34 @@ static public class IntegrityInformation { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + 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); + 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); + 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; + 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); } @@ -153,10 +170,16 @@ static public class KeyAccess { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + 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); + 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 @@ -172,10 +195,13 @@ static public class Method { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + 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); + return Objects.equals(algorithm, method.algorithm) && Objects.equals(iv, method.iv) + && Objects.equals(IsStreamable, method.IsStreamable); } @Override @@ -184,8 +210,6 @@ public int hashCode() { } } - - static public class EncryptionInformation { @SerializedName(value = "type") public String keyAccessType; @@ -198,10 +222,14 @@ static public class EncryptionInformation { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + 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); + 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 @@ -219,10 +247,14 @@ static public class Payload { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + 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); + 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 @@ -237,8 +269,10 @@ static public class Binding { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; Binding binding = (Binding) o; return Objects.equals(method, binding.method) && Objects.equals(signature, binding.signature); } @@ -266,15 +300,21 @@ public HashValues(String assertionHash, String signature) { this.signature = signature; } - public String getAssertionHash() { return assertionHash; } - public String getSignature() { return signature; } - } + public String getAssertionHash() { + return assertionHash; + } + public String getSignature() { + return signature; + } + } @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; Assertion that = (Assertion) o; return Objects.equals(id, that.id) && Objects.equals(type, that.type) && Objects.equals(scope, that.scope) && Objects.equals(appliesToState, that.appliesToState) && @@ -303,7 +343,8 @@ public String hash() throws IOException { // Sign the assertion with the given hash and signature using the key. // It returns an error if the signing fails. // The assertion binding is updated with the method and the signature. - public void sign(final HashValues hashValues, final AssertionConfig.AssertionKey assertionKey) throws KeyLengthException { + public void sign(final HashValues hashValues, final AssertionConfig.AssertionKey assertionKey) + throws KeyLengthException { // Build JWT claims final JWTClaimsSet claims = new JWTClaimsSet.Builder() .claim(kAssertionHash, hashValues.assertionHash) @@ -327,8 +368,10 @@ public void sign(final HashValues hashValues, final AssertionConfig.AssertionKey } // Checks the binding signature of the assertion and - // returns the hash and the signature. It returns an error if the verification fails. - public Assertion.HashValues verify(AssertionConfig.AssertionKey assertionKey) throws ParseException, JOSEException { + // returns the hash and the signature. It returns an error if the verification + // fails. + public Assertion.HashValues verify(AssertionConfig.AssertionKey assertionKey) + throws ParseException, JOSEException { if (binding == null) { throw new SDKException("Binding is null in assertion"); } @@ -350,7 +393,8 @@ public Assertion.HashValues verify(AssertionConfig.AssertionKey assertionKey) th return new Assertion.HashValues(assertionHash, signature); } - private SignedJWT createSignedJWT(final JWTClaimsSet claims, final AssertionConfig.AssertionKey assertionKey) throws SDKException { + private SignedJWT createSignedJWT(final JWTClaimsSet claims, final AssertionConfig.AssertionKey assertionKey) + throws SDKException { final JWSHeader jwsHeader; switch (assertionKey.alg) { case RS256: @@ -366,7 +410,8 @@ private SignedJWT createSignedJWT(final JWTClaimsSet claims, final AssertionConf return new SignedJWT(jwsHeader, claims); } - private JWSSigner createSigner(final AssertionConfig.AssertionKey assertionKey) throws SDKException, KeyLengthException { + private JWSSigner createSigner(final AssertionConfig.AssertionKey assertionKey) + throws SDKException, KeyLengthException { switch (assertionKey.alg) { case RS256: if (!(assertionKey.key instanceof PrivateKey)) { @@ -397,5 +442,5 @@ private JWSVerifier createVerifier(AssertionConfig.AssertionKey assertionKey) th public EncryptionInformation encryptionInformation; public Payload payload; - public List assertions = new ArrayList<>(); + public List assertions = new ArrayList<>(); } diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/NanoTDF.java b/sdk/src/main/java/io/opentdf/platform/sdk/NanoTDF.java index 168f6a69..9132e570 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/NanoTDF.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/NanoTDF.java @@ -15,16 +15,17 @@ import org.bouncycastle.jce.interfaces.ECPublicKey; import org.slf4j.Logger; import org.slf4j.LoggerFactory; + public class NanoTDF { public static Logger logger = LoggerFactory.getLogger(NanoTDF.class); - public static final byte[] MAGIC_NUMBER_AND_VERSION = new byte[]{0x4C, 0x31, 0x4C}; - private static final int kMaxTDFSize = ((16 * 1024 * 1024) - 3 - 32); // 16 mb - 3(iv) - 32(max auth tag) + public static final byte[] MAGIC_NUMBER_AND_VERSION = new byte[] { 0x4C, 0x31, 0x4C }; + private static final int kMaxTDFSize = ((16 * 1024 * 1024) - 3 - 32); // 16 mb - 3(iv) - 32(max auth tag) private static final int kNanoTDFGMACLength = 8; private static final int kIvPadding = 9; private static final int kNanoTDFIvSize = 3; - private static final byte[] kEmptyIV = new byte[] { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}; + private static final byte[] kEmptyIV = new byte[] { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 }; public static class NanoTDFMaxSizeLimit extends Exception { public NanoTDFMaxSizeLimit(String errorMessage) { @@ -45,8 +46,8 @@ public InvalidNanoTDFConfig(String errorMessage) { } public int createNanoTDF(ByteBuffer data, OutputStream outputStream, - Config.NanoTDFConfig nanoTDFConfig, - SDK.KAS kas) throws IOException, NanoTDFMaxSizeLimit, InvalidNanoTDFConfig, + Config.NanoTDFConfig nanoTDFConfig, + SDK.KAS kas) throws IOException, NanoTDFMaxSizeLimit, InvalidNanoTDFConfig, NoSuchAlgorithmException, UnsupportedNanoTDFFeature { int nanoTDFSize = 0; @@ -137,7 +138,7 @@ public int createNanoTDF(ByteBuffer data, OutputStream outputStream, // Write the length of the payload as int24 int cipherDataLengthWithoutPadding = cipherData.length - kIvPadding; - byte[] bgIntAsBytes = ByteBuffer.allocate(4).putInt(cipherDataLengthWithoutPadding).array(); + byte[] bgIntAsBytes = ByteBuffer.allocate(4).putInt(cipherDataLengthWithoutPadding).array(); outputStream.write(bgIntAsBytes, 1, 3); nanoTDFSize += 3; @@ -151,7 +152,7 @@ public int createNanoTDF(ByteBuffer data, OutputStream outputStream, } public void readNanoTDF(ByteBuffer nanoTDF, OutputStream outputStream, - SDK.KAS kas) throws IOException { + SDK.KAS kas) throws IOException { Header header = new Header(nanoTDF); @@ -164,7 +165,7 @@ public void readNanoTDF(ByteBuffer nanoTDF, OutputStream outputStream, String kasUrl = header.getKasLocator().getResourceUrl(); - byte[] key = kas.unwrapNanoTDF(header.getECCMode().getEllipticCurveType(), + byte[] key = kas.unwrapNanoTDF(header.getECCMode().getEllipticCurveType(), base64HeaderData, kasUrl); logger.debug("readNanoTDF key is {}", Base64.getEncoder().encodeToString(key)); @@ -200,7 +201,7 @@ PolicyObject createPolicyObject(List attributes) { policyObject.body.dataAttributes = new ArrayList<>(); policyObject.body.dissem = new ArrayList<>(); - for (String attribute: attributes) { + for (String attribute : attributes) { PolicyObject.AttributeObject attributeObject = new PolicyObject.AttributeObject(); attributeObject.attribute = attribute; policyObject.body.dataAttributes.add(attributeObject); diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/PolicyObject.java b/sdk/src/main/java/io/opentdf/platform/sdk/PolicyObject.java index 8b1bb33f..15d2d0bb 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/PolicyObject.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/PolicyObject.java @@ -1,7 +1,6 @@ package io.opentdf.platform.sdk; import java.util.List; -import java.util.UUID; public class PolicyObject { static public class AttributeObject { diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java b/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java index dcde93ab..fe2a8005 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java @@ -22,8 +22,10 @@ import java.util.Optional; /** - * The SDK class represents a software development kit for interacting with the opentdf platform. It - * provides various services and stubs for making API calls to the opentdf platform. + * The SDK class represents a software development kit for interacting with the + * opentdf platform. It + * provides various services and stubs for making API calls to the opentdf + * platform. */ public class SDK implements AutoCloseable { private final Services services; @@ -39,19 +41,28 @@ public void close() throws Exception { public interface KAS extends AutoCloseable { Config.KASInfo getPublicKey(Config.KASInfo kasInfo); + Config.KASInfo getECPublicKey(Config.KASInfo kasInfo, NanoTDFType.ECCurve curve); + byte[] unwrap(Manifest.KeyAccess keyAccess, String policy); + byte[] unwrapNanoTDF(NanoTDFType.ECCurve curve, String header, String kasURL); + KASKeyCache getKeyCache(); } // TODO: add KAS public interface Services extends AutoCloseable { AuthorizationServiceFutureStub authorization(); + AttributesServiceFutureStub attributes(); + NamespaceServiceFutureStub namespaces(); + SubjectMappingServiceFutureStub subjectMappings(); + ResourceMappingServiceFutureStub resourceMappings(); + KAS kas(); static Services newServices(ManagedChannel channel, KAS kas) { @@ -120,8 +131,10 @@ public Services getServices() { } /** - * Checks to see if this has the structure of a Z-TDF in that it is a zip file containing + * Checks to see if this has the structure of a Z-TDF in that it is a zip file + * containing * a `manifest.json` and a `0.payload` + * * @param channel A channel containing the bytes of the potential Z-TDF * @return `true` if */ diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java index ad156f42..be13bdd0 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java @@ -60,37 +60,37 @@ public SDKBuilder sslFactory(SSLFactory sslFactory) { /** * Add SSL Context with trusted certs from certDirPath + * * @param certsDirPath Path to a directory containing .pem or .crt trusted certs * @return */ - public SDKBuilder sslFactoryFromDirectory(String certsDirPath) throws Exception{ + public SDKBuilder sslFactoryFromDirectory(String certsDirPath) throws Exception { File certsDir = new File(certsDirPath); - File[] certFiles = - certsDir.listFiles((dir, name) -> name.endsWith(".pem") || name.endsWith(".crt")); + File[] certFiles = certsDir.listFiles((dir, name) -> name.endsWith(".pem") || name.endsWith(".crt")); logger.info("Loading certificates from: " + certsDir.getAbsolutePath()); List certStreams = new ArrayList<>(); for (File certFile : certFiles) { certStreams.add(new FileInputStream(certFile)); } - X509ExtendedTrustManager trustManager = - PemUtils.loadTrustMaterial(certStreams.toArray(new InputStream[0])); - this.sslFactory = - SSLFactory.builder().withDefaultTrustMaterial().withSystemTrustMaterial() - .withTrustMaterial(trustManager).build(); + X509ExtendedTrustManager trustManager = PemUtils.loadTrustMaterial(certStreams.toArray(new InputStream[0])); + this.sslFactory = SSLFactory.builder().withDefaultTrustMaterial().withSystemTrustMaterial() + .withTrustMaterial(trustManager).build(); return this; } /** - * Add SSL Context with default system trust material + certs contained in a Java keystore - * @param keystorePath Path to keystore + * Add SSL Context with default system trust material + certs contained in a + * Java keystore + * + * @param keystorePath Path to keystore * @param keystorePassword Password to keystore * @return */ public SDKBuilder sslFactoryFromKeyStore(String keystorePath, String keystorePassword) { - this.sslFactory = - SSLFactory.builder().withDefaultTrustMaterial().withSystemTrustMaterial() - .withTrustMaterial(Path.of(keystorePath), keystorePassword==null ? - "".toCharArray() : keystorePassword.toCharArray()).build(); + this.sslFactory = SSLFactory.builder().withDefaultTrustMaterial().withSystemTrustMaterial() + .withTrustMaterial(Path.of(keystorePath), + keystorePassword == null ? "".toCharArray() : keystorePassword.toCharArray()) + .build(); return this; } @@ -117,12 +117,14 @@ private GRPCAuthInterceptor getGrpcAuthInterceptor(RSAKey rsaKey) { } if (clientAuth == null) { - // this simplifies things for now, if we need to support this case we can revisit + // 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 + // we don't add the auth listener to this channel since it is only used to call + // the + // well known endpoint ManagedChannel bootstrapChannel = null; GetWellKnownConfigurationResponse config; try { @@ -148,7 +150,9 @@ private GRPCAuthInterceptor getGrpcAuthInterceptor(RSAKey rsaKey) { .getStringValue(); } catch (IllegalArgumentException e) { - logger.warn("no `platform_issuer` found in well known configuration. requests from the SDK will be unauthenticated", e); + logger.warn( + "no `platform_issuer` found in well known configuration. requests from the SDK will be unauthenticated", + e); return null; } @@ -156,7 +160,7 @@ private GRPCAuthInterceptor getGrpcAuthInterceptor(RSAKey rsaKey) { OIDCProviderMetadata providerMetadata; try { providerMetadata = OIDCProviderMetadata.resolve(issuer, httpRequest -> { - if (sslFactory!=null) { + if (sslFactory != null) { httpRequest.setSSLSocketFactory(sslFactory.getSslSocketFactory()); } }); @@ -200,14 +204,14 @@ ServicesAndInternals buildServices() { } else { channel = getManagedChannelBuilder(platformEndpoint).intercept(authInterceptor).build(); - managedChannelFactory = (String endpoint) -> getManagedChannelBuilder(endpoint).intercept(authInterceptor).build(); + managedChannelFactory = (String endpoint) -> getManagedChannelBuilder(endpoint).intercept(authInterceptor) + .build(); } var client = new KASClient(managedChannelFactory, dpopKey); return new ServicesAndInternals( authInterceptor, sslFactory == null ? null : sslFactory.getTrustManager().orElse(null), - SDK.Services.newServices(channel, client) - ); + SDK.Services.newServices(channel, client)); } public SDK build() { @@ -216,9 +220,12 @@ public SDK build() { } /** - * This produces a channel configured with all the available SDK options. The only - * reason it can't take in an interceptor is because we need to create a channel that + * This produces a channel configured with all the available SDK options. The + * only + * reason it can't take in an interceptor is because we need to create a channel + * that * doesn't have any authentication when we are bootstrapping + * * @param endpoint The endpoint that we are creating the channel for * @return {@type ManagedChannelBuilder} configured with the SDK options */ @@ -227,7 +234,7 @@ private ManagedChannelBuilder getManagedChannelBuilder(String endpoint) { if (sslFactory != null && !usePlainText) { channelBuilder = Grpc.newChannelBuilder(endpoint, TlsChannelCredentials.newBuilder() .trustManager(sslFactory.getTrustManager().get()).build()); - }else{ + } else { channelBuilder = ManagedChannelBuilder.forTarget(endpoint); } @@ -237,7 +244,7 @@ private ManagedChannelBuilder getManagedChannelBuilder(String endpoint) { return channelBuilder; } - SSLFactory getSslFactory(){ + SSLFactory getSslFactory() { return this.sslFactory; } } diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java b/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java index 41bba40b..e7c8aff5 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java @@ -1,12 +1,7 @@ package io.opentdf.platform.sdk; - import com.google.gson.Gson; 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 io.opentdf.platform.policy.Value; import io.opentdf.platform.policy.attributes.AttributesServiceGrpc.AttributesServiceFutureStub; @@ -14,8 +9,6 @@ import io.opentdf.platform.sdk.Autoconfigure.AttributeValueFQN; import io.opentdf.platform.sdk.Config.KASInfo; -import com.nimbusds.jose.crypto.RSASSASigner; -import com.nimbusds.jose.crypto.MACSigner; import org.apache.commons.codec.DecoderException; import org.apache.commons.codec.binary.Hex; import org.erdtman.jcs.JsonCanonicalizer; @@ -29,7 +22,6 @@ import java.nio.channels.SeekableByteChannel; import java.nio.charset.StandardCharsets; import java.security.*; -import java.util.function.Consumer; import java.text.ParseException; import java.util.*; import java.util.concurrent.ExecutionException; @@ -42,7 +34,8 @@ public TDF() { this(MAX_TDF_INPUT_SIZE); } - // constructor for tests so that we can set a maximum size that's tractable for tests + // constructor for tests so that we can set a maximum size that's tractable for + // tests TDF(long maximumInputSize) { this.maximumSize = maximumInputSize; } @@ -54,7 +47,7 @@ public TDF() { private static final String kSplitKeyType = "split"; private static final String kWrapped = "wrapped"; private static final String kKasProtocol = "kas"; - private static final int kGcmIvSize = 12; + private static final int kGcmIvSize = 12; private static final int kAesBlockSize = 16; private static final String kGCMCipherAlgorithm = "AES-256-GCM"; private static final int kGMACPayloadLength = 16; @@ -69,7 +62,7 @@ public TDF() { private static final Gson gson = new Gson(); - public class SplitKeyException extends IOException { + public class SplitKeyException extends IOException { public SplitKeyException(String errorMessage) { super(errorMessage); } @@ -135,7 +128,6 @@ public TDFReadFailed(String errorMessage) { } } - public static class EncryptedMetadata { private String ciphertext; private String iv; @@ -162,7 +154,7 @@ PolicyObject createPolicyObject(List attributes policyObject.body.dataAttributes = new ArrayList<>(); policyObject.body.dissem = new ArrayList<>(); - for (Autoconfigure.AttributeValueFQN attribute: attributes) { + for (Autoconfigure.AttributeValueFQN attribute : attributes) { PolicyObject.AttributeObject attributeObject = new PolicyObject.AttributeObject(); attributeObject.attribute = attribute.toString(); policyObject.body.dataAttributes.add(attributeObject); @@ -171,12 +163,14 @@ PolicyObject createPolicyObject(List attributes } private static final Base64.Encoder encoder = Base64.getEncoder(); + private void prepareManifest(Config.TDFConfig tdfConfig, SDK.KAS kas) { manifest.encryptionInformation.keyAccessType = kSplitKeyType; - manifest.encryptionInformation.keyAccessObj = new ArrayList<>(); + manifest.encryptionInformation.keyAccessObj = new ArrayList<>(); PolicyObject policyObject = createPolicyObject(tdfConfig.attributes); - String base64PolicyObject = encoder.encodeToString(gson.toJson(policyObject).getBytes(StandardCharsets.UTF_8)); + String base64PolicyObject = encoder + .encodeToString(gson.toJson(policyObject).getBytes(StandardCharsets.UTF_8)); List symKeys = new ArrayList<>(); Map latestKASInfo = new HashMap<>(); if (tdfConfig.splitPlan == null || tdfConfig.splitPlan.isEmpty()) { @@ -197,7 +191,7 @@ private void prepareManifest(Config.TDFConfig tdfConfig, SDK.KAS kas) { } // Seed anything passed in manually - for (Config.KASInfo kasInfo: tdfConfig.kasInfoList) { + for (Config.KASInfo kasInfo : tdfConfig.kasInfoList) { if (kasInfo.PublicKey != null && !kasInfo.PublicKey.isEmpty()) { latestKASInfo.put(kasInfo.URL, kasInfo); } @@ -230,22 +224,22 @@ private void prepareManifest(Config.TDFConfig tdfConfig, SDK.KAS kas) { } } - for (String splitID: splitIDs) { + for (String splitID : splitIDs) { // Symmetric key byte[] symKey = new byte[GCM_KEY_SIZE]; sRandom.nextBytes(symKey); symKeys.add(symKey); // Add policyBinding - var hexBinding = Hex.encodeHexString(CryptoUtils.CalculateSHA256Hmac(symKey, base64PolicyObject.getBytes(StandardCharsets.UTF_8))); + var hexBinding = Hex.encodeHexString( + CryptoUtils.CalculateSHA256Hmac(symKey, base64PolicyObject.getBytes(StandardCharsets.UTF_8))); var policyBinding = new Manifest.PolicyBinding(); policyBinding.alg = kHmacIntegrityAlgorithm; policyBinding.hash = encoder.encodeToString(hexBinding.getBytes(StandardCharsets.UTF_8)); - // Add meta data var encryptedMetadata = new String(); - if(tdfConfig.metaData != null && !tdfConfig.metaData.trim().isEmpty()) { + if (tdfConfig.metaData != null && !tdfConfig.metaData.trim().isEmpty()) { AesGcm aesGcm = new AesGcm(symKey); var encrypted = aesGcm.encrypt(tdfConfig.metaData.getBytes(StandardCharsets.UTF_8)); @@ -257,7 +251,7 @@ private void prepareManifest(Config.TDFConfig tdfConfig, SDK.KAS kas) { encryptedMetadata = encoder.encodeToString(metadata.getBytes(StandardCharsets.UTF_8)); } - for (Config.KASInfo kasInfo: conjunction.get(splitID)){ + for (Config.KASInfo kasInfo : conjunction.get(splitID)) { if (kasInfo.PublicKey == null || kasInfo.PublicKey.isEmpty()) { throw new KasPublicKeyMissing("Kas public key is missing in kas information list"); } @@ -284,7 +278,7 @@ private void prepareManifest(Config.TDFConfig tdfConfig, SDK.KAS kas) { manifest.encryptionInformation.method.algorithm = kGCMCipherAlgorithm; // Create the payload key by XOR all the keys in key access object. - for (byte[] symKey: symKeys) { + for (byte[] symKey : symKeys) { for (int index = 0; index < symKey.length; index++) { this.payloadKey[index] ^= symKey[index]; } @@ -295,10 +289,12 @@ private void prepareManifest(Config.TDFConfig tdfConfig, SDK.KAS kas) { } private static final Base64.Decoder decoder = Base64.getDecoder(); + public static class Reader { private final TDFReader tdfReader; private final byte[] payloadKey; private final Manifest manifest; + public String getMetadata() { return unencryptedMetadata; } @@ -324,8 +320,8 @@ public void readPayload(OutputStream outputStream) throws TDFReadFailed, MessageDigest digest = MessageDigest.getInstance("SHA-256"); - for (Manifest.Segment segment: manifest.encryptionInformation.integrityInformation.segments) { - byte[] readBuf = new byte[(int)segment.encryptedSegmentSize]; + for (Manifest.Segment segment : manifest.encryptionInformation.integrityInformation.segments) { + byte[] readBuf = new byte[(int) segment.encryptedSegmentSize]; int bytesRead = tdfReader.readPayloadBytes(readBuf); if (readBuf.length != bytesRead) { @@ -376,30 +372,33 @@ private static String calculateSignature(byte[] data, byte[] secret, Config.Inte } public TDFObject createTDF(InputStream payload, - OutputStream outputStream, - Config.TDFConfig tdfConfig, SDK.KAS kas, AttributesServiceFutureStub attrService) throws IOException, JOSEException, AutoConfigureException, InterruptedException, ExecutionException { + OutputStream outputStream, + Config.TDFConfig tdfConfig, SDK.KAS kas, AttributesServiceFutureStub attrService) + throws IOException, JOSEException, AutoConfigureException, InterruptedException, ExecutionException { if (tdfConfig.autoconfigure) { Autoconfigure.Granter granter = new Autoconfigure.Granter(new ArrayList<>()); if (tdfConfig.attributeValues != null && !tdfConfig.attributeValues.isEmpty()) { granter = Autoconfigure.newGranterFromAttributes(tdfConfig.attributeValues.toArray(new Value[0])); } else if (tdfConfig.attributes != null && !tdfConfig.attributes.isEmpty()) { - granter = Autoconfigure.newGranterFromService(attrService, kas.getKeyCache(), tdfConfig.attributes.toArray(new AttributeValueFQN[0])); + granter = Autoconfigure.newGranterFromService(attrService, kas.getKeyCache(), + tdfConfig.attributes.toArray(new AttributeValueFQN[0])); } - + if (granter == null) { throw new AutoConfigureException("Failed to create Granter"); // Replace with appropriate error handling } - + List dk = defaultKases(tdfConfig); tdfConfig.splitPlan = granter.plan(dk, () -> UUID.randomUUID().toString()); - + if (tdfConfig.splitPlan == null) { - throw new AutoConfigureException("Failed to generate Split Plan"); // Replace with appropriate error handling + throw new AutoConfigureException("Failed to generate Split Plan"); // Replace with appropriate error + // handling } } - if (tdfConfig.kasInfoList.isEmpty() && (tdfConfig.splitPlan==null || tdfConfig.splitPlan.isEmpty())) { + if (tdfConfig.kasInfoList.isEmpty() && (tdfConfig.splitPlan == null || tdfConfig.splitPlan.isEmpty())) { throw new KasInfoMissing("kas information is missing, no key access template specified or inferred"); } @@ -426,7 +425,8 @@ public TDFObject createTDF(InputStream payload, do { int nRead = 0; int readThisLoop = 0; - while (readThisLoop < readBuf.length && (nRead = payload.read(readBuf, readThisLoop, readBuf.length - readThisLoop)) > 0) { + while (readThisLoop < readBuf.length + && (nRead = payload.read(readBuf, readThisLoop, readBuf.length - readThisLoop)) > 0) { readThisLoop += nRead; } finished = nRead < 0; @@ -469,7 +469,7 @@ public TDFObject createTDF(InputStream payload, tdfObject.manifest.encryptionInformation.integrityInformation.rootSignature = rootSignature; tdfObject.manifest.encryptionInformation.integrityInformation.segmentSizeDefault = tdfConfig.defaultSegmentSize; - tdfObject.manifest.encryptionInformation.integrityInformation.encryptedSegmentSizeDefault = (int)encryptedSegmentSize; + tdfObject.manifest.encryptionInformation.integrityInformation.encryptedSegmentSizeDefault = (int) encryptedSegmentSize; tdfObject.manifest.encryptionInformation.integrityInformation.segmentHashAlg = kGmacIntegrityAlgorithm; if (tdfConfig.segmentIntegrityAlgorithm == Config.IntegrityAlgorithm.HS256) { @@ -486,8 +486,9 @@ public TDFObject createTDF(InputStream payload, tdfObject.manifest.payload.url = TDFWriter.TDF_PAYLOAD_FILE_NAME; tdfObject.manifest.payload.isEncrypted = true; - List signedAssertions = new ArrayList<>();; - for (var assertionConfig: tdfConfig.assertionConfigList) { + List signedAssertions = new ArrayList<>(); + ; + for (var assertionConfig : tdfConfig.assertionConfigList) { var assertion = new Manifest.Assertion(); assertion.id = assertionConfig.id; assertion.type = assertionConfig.type.toString(); @@ -538,7 +539,7 @@ public List defaultKases(TDFConfig config) { } private void fillInPublicKeyInfo(List kasInfoList, SDK.KAS kas) { - for (var kasInfo: kasInfoList) { + for (var kasInfo : kasInfoList) { if (kasInfo.PublicKey != null && !kasInfo.PublicKey.isBlank()) { continue; } @@ -549,7 +550,9 @@ private void fillInPublicKeyInfo(List kasInfoList, SDK.KAS kas) } } - public Reader loadTDF(SeekableByteChannel tdf, SDK.KAS kas, Config.AssertionVerificationKeys... assertionVerificationKeys) throws NotValidateRootSignature, SegmentSizeMismatch, + public Reader loadTDF(SeekableByteChannel tdf, SDK.KAS kas, + Config.AssertionVerificationKeys... assertionVerificationKeys) + throws NotValidateRootSignature, SegmentSizeMismatch, IOException, FailedToCreateGMAC, JOSEException, ParseException, NoSuchAlgorithmException, DecoderException { TDFReader tdfReader = new TDFReader(tdf); @@ -557,25 +560,26 @@ public Reader loadTDF(SeekableByteChannel tdf, SDK.KAS kas, Config.AssertionVeri Manifest manifest = gson.fromJson(manifestJson, Manifest.class); byte[] payloadKey = new byte[GCM_KEY_SIZE]; String unencryptedMetadata = null; - + Set knownSplits = new HashSet(); - Set foundSplits = new HashSet();; + Set foundSplits = new HashSet(); + ; Map skippedSplits = new HashMap<>(); boolean mixedSplits = manifest.encryptionInformation.keyAccessObj.size() > 1 && - (manifest.encryptionInformation.keyAccessObj.get(0).sid != null) && - !manifest.encryptionInformation.keyAccessObj.get(0).sid.isEmpty(); + (manifest.encryptionInformation.keyAccessObj.get(0).sid != null) && + !manifest.encryptionInformation.keyAccessObj.get(0).sid.isEmpty(); - MessageDigest digest = MessageDigest.getInstance("SHA-256"); + MessageDigest digest = MessageDigest.getInstance("SHA-256"); if (manifest.payload.isEncrypted) { - for (Manifest.KeyAccess keyAccess: manifest.encryptionInformation.keyAccessObj) { + for (Manifest.KeyAccess keyAccess : manifest.encryptionInformation.keyAccessObj) { Autoconfigure.KeySplitStep ss = new Autoconfigure.KeySplitStep(keyAccess.url, keyAccess.sid); byte[] unwrappedKey; if (!mixedSplits) { unwrappedKey = kas.unwrap(keyAccess, manifest.encryptionInformation.policy); } else { knownSplits.add(unencryptedMetadata); - if (foundSplits.contains(ss.splitID)){ + if (foundSplits.contains(ss.splitID)) { continue; } try { @@ -585,7 +589,7 @@ public Reader loadTDF(SeekableByteChannel tdf, SDK.KAS kas, Config.AssertionVeri continue; } } - + for (int index = 0; index < unwrappedKey.length; index++) { payloadKey[index] ^= unwrappedKey[index]; } @@ -594,16 +598,18 @@ public Reader loadTDF(SeekableByteChannel tdf, SDK.KAS kas, Config.AssertionVeri if (keyAccess.encryptedMetadata != null && !keyAccess.encryptedMetadata.isEmpty()) { AesGcm aesGcm = new AesGcm(unwrappedKey); - String decodedMetadata = new String(Base64.getDecoder().decode(keyAccess.encryptedMetadata), "UTF-8"); + String decodedMetadata = new String(Base64.getDecoder().decode(keyAccess.encryptedMetadata), + "UTF-8"); EncryptedMetadata encryptedMetadata = gson.fromJson(decodedMetadata, EncryptedMetadata.class); var encryptedData = new AesGcm.Encrypted( - decoder.decode(encryptedMetadata.ciphertext) - ); + decoder.decode(encryptedMetadata.ciphertext)); byte[] decrypted = aesGcm.decrypt(encryptedData); - // this is a little bit weird... the last unencrypted metadata we get from a KAS is the one - // that we return to the user. This is OK because we can't have different metadata per-KAS + // this is a little bit weird... the last unencrypted metadata we get from a KAS + // is the one + // that we return to the user. This is OK because we can't have different + // metadata per-KAS unencryptedMetadata = new String(decrypted, StandardCharsets.UTF_8); } } @@ -611,16 +617,16 @@ public Reader loadTDF(SeekableByteChannel tdf, SDK.KAS kas, Config.AssertionVeri if (mixedSplits && knownSplits.size() > foundSplits.size()) { List exceptionList = new ArrayList<>(skippedSplits.size() + 1); exceptionList.add(new Exception("splitKey.unable to reconstruct split key: " + skippedSplits)); - + for (Map.Entry entry : skippedSplits.entrySet()) { exceptionList.add(entry.getValue()); } - + StringBuilder combinedMessage = new StringBuilder(); for (Exception e : exceptionList) { combinedMessage.append(e.getMessage()).append("\n"); } - + throw new SplitKeyException(combinedMessage.toString()); } } @@ -630,7 +636,7 @@ public Reader loadTDF(SeekableByteChannel tdf, SDK.KAS kas, Config.AssertionVeri String rootSignature = manifest.encryptionInformation.integrityInformation.rootSignature.signature; ByteArrayOutputStream aggregateHash = new ByteArrayOutputStream(); - for (Manifest.Segment segment: manifest.encryptionInformation.integrityInformation.segments) { + for (Manifest.Segment segment : manifest.encryptionInformation.integrityInformation.segments) { if (manifest.payload.isEncrypted) { byte[] decodedHash = Base64.getDecoder().decode(segment.hash); aggregateHash.write(decodedHash); @@ -649,8 +655,7 @@ public Reader loadTDF(SeekableByteChannel tdf, SDK.KAS kas, Config.AssertionVeri var sig = calculateSignature(aggregateHash.toByteArray(), payloadKey, sigAlg); rootSigValue = Base64.getEncoder().encodeToString(sig.getBytes(StandardCharsets.UTF_8)); - } - else { + } else { rootSigValue = Base64.getEncoder().encodeToString(digest.digest(aggregateHash.toString().getBytes())); } @@ -666,7 +671,7 @@ public Reader loadTDF(SeekableByteChannel tdf, SDK.KAS kas, Config.AssertionVeri } // Validate assertions - for (var assertion: manifest.assertions) { + for (var assertion : manifest.assertions) { // Set default to HS256 var assertionKey = new AssertionConfig.AssertionKey(AssertionConfig.AssertionKeyAlg.HS256, payloadKey); if (assertionVerificationKeys != null && assertionVerificationKeys.length > 0) { diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/TDFReader.java b/sdk/src/main/java/io/opentdf/platform/sdk/TDFReader.java index b74c7beb..c83a8bd9 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/TDFReader.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/TDFReader.java @@ -1,11 +1,8 @@ package io.opentdf.platform.sdk; import java.io.ByteArrayOutputStream; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; -import java.io.OutputStream; -import java.nio.ByteBuffer; import java.nio.channels.SeekableByteChannel; import java.nio.charset.StandardCharsets; import java.util.stream.Collectors; diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/nanotdf/ECKeyPair.java b/sdk/src/main/java/io/opentdf/platform/sdk/nanotdf/ECKeyPair.java index 4aaa2b8c..72641e68 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/nanotdf/ECKeyPair.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/nanotdf/ECKeyPair.java @@ -20,11 +20,9 @@ import org.bouncycastle.util.io.pem.PemReader; import org.bouncycastle.jce.spec.ECPublicKeySpec; - import javax.crypto.KeyAgreement; import java.io.*; import java.security.*; -import java.security.cert.CertificateException; import java.security.spec.*; // https://www.bouncycastle.org/latest_releases.html @@ -33,6 +31,7 @@ public class ECKeyPair { static { Security.addProvider(new BouncyCastleProvider()); } + public enum ECAlgorithm { ECDH, ECDSA @@ -57,6 +56,7 @@ public String toString() { return name; } } + private KeyPair keyPair; private String curveName; @@ -68,7 +68,8 @@ public ECKeyPair(String curveName, ECAlgorithm algorithm) { KeyPairGenerator generator; try { - // Should this just use the algorithm vs use ECDH only for ECDH and ECDSA for everything else. + // Should this just use the algorithm vs use ECDH only for ECDH and ECDSA for + // everything else. if (algorithm == ECAlgorithm.ECDH) { generator = KeyPairGeneratorSpi.getInstance(ECAlgorithm.ECDH.name(), BOUNCY_CASTLE_PROVIDER); } else { @@ -94,11 +95,11 @@ public ECKeyPair(ECPublicKey publicKey, ECPrivateKey privateKey, String curveNam } public ECPublicKey getPublicKey() { - return (ECPublicKey)this.keyPair.getPublic(); + return (ECPublicKey) this.keyPair.getPublic(); } public ECPrivateKey getPrivateKey() { - return (ECPrivateKey)this.keyPair.getPrivate(); + return (ECPrivateKey) this.keyPair.getPrivate(); } public static int getECKeySize(String curveName) { @@ -114,7 +115,7 @@ public static int getECKeySize(String curveName) { } } - public String publicKeyInPEMFormat() { + public String publicKeyInPEMFormat() { StringWriter writer = new StringWriter(); PemWriter pemWriter = new PemWriter(writer); @@ -149,11 +150,11 @@ public int keySize() { } public String curveName() { - return this.curveName = curveName; + return this.curveName; } - public byte[] compressECPublickey() { - return ((ECPublicKey)this.keyPair.getPublic()).getQ().getEncoded(true); + public byte[] compressECPublickey() { + return ((ECPublicKey) this.keyPair.getPublic()).getQ().getEncoded(true); } public static String getPEMPublicKeyFromX509Cert(String pemInX509Format) { @@ -165,7 +166,7 @@ public static String getPEMPublicKeyFromX509Cert(String pemInX509Format) { JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(BOUNCY_CASTLE_PROVIDER); ECPublicKey publicKey = null; try { - publicKey = (ECPublicKey)converter.getPublicKey(publicKeyInfo); + publicKey = (ECPublicKey) converter.getPublicKey(publicKeyInfo); } catch (PEMException e) { throw new RuntimeException(e); } @@ -189,19 +190,19 @@ public static byte[] compressECPublickey(String pemECPubKey) { PemReader pemReader = new PemReader(new StringReader(pemECPubKey)); PemObject pemObject = pemReader.readPemObject(); PublicKey pubKey = ecKeyFac.generatePublic(new X509EncodedKeySpec(pemObject.getContent())); - return ((ECPublicKey)pubKey).getQ().getEncoded(true); + return ((ECPublicKey) pubKey).getQ().getEncoded(true); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } catch (InvalidKeySpecException e) { throw new RuntimeException(e); - } catch (NoSuchProviderException e) { + } catch (NoSuchProviderException e) { throw new RuntimeException(e); } } - public static String publicKeyFromECPoint(byte[] ecPoint, String curveName) { + public static String publicKeyFromECPoint(byte[] ecPoint, String curveName) { try { // Create EC Public key ECNamedCurveParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec(curveName); @@ -244,10 +245,11 @@ public static ECPublicKey publicKeyFromPem(String pemEncoding) { public static ECPrivateKey privateKeyFromPem(String pemEncoding) { try { PEMParser parser = new PEMParser(new StringReader(pemEncoding)); - PrivateKeyInfo privateKeyInfo = (PrivateKeyInfo)parser.readObject(); + PrivateKeyInfo privateKeyInfo = (PrivateKeyInfo) parser.readObject(); parser.close(); - JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(BOUNCY_CASTLE_PROVIDER);; + JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(BOUNCY_CASTLE_PROVIDER); + ; return (ECPrivateKey) converter.getPrivateKey(privateKeyInfo); } catch (IOException e) { throw new RuntimeException(e); @@ -285,7 +287,7 @@ public static byte[] computeECDSASig(byte[] digest, ECPrivateKey privateKey) { throw new RuntimeException(e); } catch (NoSuchProviderException e) { throw new RuntimeException(e); - } catch (InvalidKeyException e) { + } catch (InvalidKeyException e) { throw new RuntimeException(e); } catch (SignatureException e) { throw new RuntimeException(e); @@ -298,11 +300,11 @@ public static Boolean verifyECDSAig(byte[] digest, byte[] signature, ECPublicKey ecdsaVerify.initVerify(publicKey); ecdsaVerify.update(digest); return ecdsaVerify.verify(signature); - } catch (NoSuchAlgorithmException e) { + } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } catch (NoSuchProviderException e) { throw new RuntimeException(e); - } catch (InvalidKeyException e) { + } catch (InvalidKeyException e) { throw new RuntimeException(e); } catch (SignatureException e) { throw new RuntimeException(e); diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/nanotdf/Header.java b/sdk/src/main/java/io/opentdf/platform/sdk/nanotdf/Header.java index 324e83e6..9328c7a9 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/nanotdf/Header.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/nanotdf/Header.java @@ -2,7 +2,6 @@ import io.opentdf.platform.sdk.NanoTDF; -import java.io.OutputStream; import java.nio.ByteBuffer; import java.util.Arrays; @@ -35,7 +34,7 @@ public Header(ByteBuffer buffer) { } public byte[] getMagicNumberAndVersion() { - return Arrays.copyOf(NanoTDF.MAGIC_NUMBER_AND_VERSION, NanoTDF.MAGIC_NUMBER_AND_VERSION.length); + return Arrays.copyOf(NanoTDF.MAGIC_NUMBER_AND_VERSION, NanoTDF.MAGIC_NUMBER_AND_VERSION.length); } public void setMagicNumberAndVersion(byte[] magicNumberAndVersion) { @@ -45,7 +44,8 @@ public void setMagicNumberAndVersion(byte[] magicNumberAndVersion) { if (!Arrays.equals(magicNumberAndVersion, NanoTDF.MAGIC_NUMBER_AND_VERSION)) { throw new IllegalArgumentException("Invalid magic number and version. It must be {0x4C, 0x31, 0x4C}."); } - System.arraycopy(magicNumberAndVersion, 0, NanoTDF.MAGIC_NUMBER_AND_VERSION, 0, NanoTDF.MAGIC_NUMBER_AND_VERSION.length); + System.arraycopy(magicNumberAndVersion, 0, NanoTDF.MAGIC_NUMBER_AND_VERSION, 0, + NanoTDF.MAGIC_NUMBER_AND_VERSION.length); } public void setKasLocator(ResourceLocator kasLocator) { @@ -128,31 +128,4 @@ public int writeIntoBuffer(ByteBuffer buffer) { return totalBytesWritten; } - -// public int writeIntoBuffer(OutputStream stream) { -// if (buffer.remaining() < getTotalSize()) { -// throw new IllegalArgumentException("Failed to write header - invalid buffer size."); -// } -// -// int totalBytesWritten = 0; -// buffer.put(NanoTDF.MAGIC_NUMBER_AND_VERSION); -// totalBytesWritten += NanoTDF.MAGIC_NUMBER_AND_VERSION.length; -// -// int kasLocatorSize = kasLocator.writeIntoBuffer(buffer); -// totalBytesWritten += kasLocatorSize; -// -// buffer.put(eccMode.getECCModeAsByte()); -// totalBytesWritten += 1; -// -// buffer.put(payloadConfig.getSymmetricAndPayloadConfigAsByte()); -// totalBytesWritten += 1; -// -// int policyInfoSize = policyInfo.writeIntoBuffer(buffer); -// totalBytesWritten += policyInfoSize; -// -// buffer.put(ephemeralKey); -// totalBytesWritten += ephemeralKey.length; -// -// return totalBytesWritten; -// } } \ No newline at end of file diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/nanotdf/PolicyInfo.java b/sdk/src/main/java/io/opentdf/platform/sdk/nanotdf/PolicyInfo.java index 799c80a6..c4b93f34 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/nanotdf/PolicyInfo.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/nanotdf/PolicyInfo.java @@ -1,9 +1,6 @@ package io.opentdf.platform.sdk.nanotdf; -import io.opentdf.platform.sdk.NanoTDF; - import java.nio.ByteBuffer; -import java.util.Arrays; public class PolicyInfo { private NanoTDFType.PolicyType type; @@ -14,7 +11,7 @@ public class PolicyInfo { public PolicyInfo() { } - public PolicyInfo(ByteBuffer buffer, ECCMode eccMode) { + public PolicyInfo(ByteBuffer buffer, ECCMode eccMode) { this.type = NanoTDFType.PolicyType.values()[buffer.get()]; this.hasECDSABinding = eccMode.isECDSABindingEnabled(); @@ -48,7 +45,7 @@ public PolicyInfo(ByteBuffer buffer, ECCMode eccMode) { } int bindingBytesSize = 8; // GMAC length - if(this.hasECDSABinding) { // ECDSA - The size of binding depends on the curve. + if (this.hasECDSABinding) { // ECDSA - The size of binding depends on the curve. bindingBytesSize = ECCMode.getECDSASignatureStructSize(eccMode.getEllipticCurveType()); } @@ -66,7 +63,7 @@ public int getTotalSize() { if (type == NanoTDFType.PolicyType.EMBEDDED_POLICY_PLAIN_TEXT || type == NanoTDFType.PolicyType.EMBEDDED_POLICY_ENCRYPTED) { - int policySize = body.length; + int policySize = body.length; totalSize = (1 + Short.BYTES + body.length + binding.length); } else { throw new RuntimeException("Embedded policy with key access is not supported."); @@ -101,13 +98,12 @@ public int writeIntoBuffer(ByteBuffer buffer) { // 1 - Length of the policy; // 2 - policy bytes itself // 3 - policy key access( ONLY for EMBEDDED_POLICY_ENCRYPTED_POLICY_KEY_ACCESS) - // 1 - resource locator - // 2 - ephemeral public key, the size depends on ECC mode. + // 1 - resource locator + // 2 - ephemeral public key, the size depends on ECC mode. // Write the length of the policy - - short policyLength = (short)body.length; + short policyLength = (short) body.length; buffer.putShort(policyLength); totalBytesWritten += Short.BYTES; diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/AsymEncryptionTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/AsymEncryptionTest.java index 49e954a0..9446732f 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/AsymEncryptionTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/AsymEncryptionTest.java @@ -2,11 +2,6 @@ import static org.junit.jupiter.api.Assertions.*; -import javax.crypto.Cipher; -import java.security.KeyFactory; -import java.security.PublicKey; -import java.security.spec.X509EncodedKeySpec; -import java.util.Base64; import org.junit.jupiter.api.Test; class AsymEncryptionTest { @@ -21,7 +16,8 @@ void encryptionWithValidPublicKey() throws Exception { "GBKh0CWGAXWRmphzGj7kFpkAxT1b827MrQMYxkn4w2WB8B/bGKz0+dWyqnnzGYAS\n" + "hVJ0rIiNE8dDWzQCRBfivLemXhX8UFICyoS5i0IwenFvTr6T85EvMxK3aSAlGya3\n" + "3wIDAQAB\n" + - "-----END PUBLIC KEY-----";; + "-----END PUBLIC KEY-----"; + ; AsymEncryption asymEncryption = new AsymEncryption(publicKeyInPem); byte[] plaintext = "Virtru, JavaSDK!".getBytes(); @@ -49,7 +45,8 @@ void encryptionWithValidCert() throws Exception { "OcL/UAwQ2pXmfEFjYBs5mDEpKwGC0DxW4tg0FIsb3bbAvqy8ETklExkOh0VfJP4a\n" + "CMz9WjmCfS15t0mPzofK8ir20kF0u0sWvviVVlun+8KYdFOG/wzS100cPNn/wqug\n" + "4w==\n" + - "-----END CERTIFICATE-----";; + "-----END CERTIFICATE-----"; + ; AsymEncryption asymEncryption = new AsymEncryption(certInPem); byte[] plaintext = "Virtru, JavaSDK!".getBytes(); diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/AutoconfigureTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/AutoconfigureTest.java index 7f761ac5..0cc7babe 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/AutoconfigureTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/AutoconfigureTest.java @@ -88,7 +88,6 @@ public static void setup() throws AutoConfigureException { UNSPECKED = new Autoconfigure.AttributeNameFQN("https://other.com/attr/unspecified"); SPECKED = new Autoconfigure.AttributeNameFQN("https://other.com/attr/specified"); - clsA = new Autoconfigure.AttributeValueFQN("https://virtru.com/attr/Classification/value/Allowed"); clsS = new Autoconfigure.AttributeValueFQN("https://virtru.com/attr/Classification/value/Secret"); clsTS = new Autoconfigure.AttributeValueFQN("https://virtru.com/attr/Classification/value/Top%20Secret"); @@ -146,7 +145,7 @@ private static String spongeCase(String s) { private List valuesToPolicy(AttributeValueFQN... p) throws AutoConfigureException { List values = new ArrayList<>(); - for (AttributeValueFQN afqn : List.of(p)){ + for (AttributeValueFQN afqn : List.of(p)) { values.add(mockValueFor(afqn)); } return values; @@ -154,8 +153,8 @@ private List valuesToPolicy(AttributeValueFQN... p) throws AutoConfigureE private List policyToStringKeys(List policy) { return policy.stream() - .map(AttributeValueFQN::getKey) - .collect(Collectors.toList()); + .map(AttributeValueFQN::getKey) + .collect(Collectors.toList()); } private Autoconfigure.AttributeValueFQN messUpV(Autoconfigure.AttributeValueFQN a) { @@ -170,29 +169,29 @@ private Attribute mockAttributeFor(Autoconfigure.AttributeNameFQN fqn) { Namespace ns1 = Namespace.newBuilder().setId("v").setName("virtru.com").setFqn("https://virtru.com").build(); Namespace ns2 = Namespace.newBuilder().setId("o").setName("other.com").setFqn("https://other.com").build(); String key = fqn.getKey(); - if (key.equals(CLS.getKey())){ - return Attribute.newBuilder().setId("CLS").setNamespace(ns1) - .setName("Classification").setRule(AttributeRuleTypeEnum.ATTRIBUTE_RULE_TYPE_ENUM_HIERARCHY).setFqn(fqn.toString()).build(); - } - else if (key.equals(N2K.getKey())) { + if (key.equals(CLS.getKey())) { + return Attribute.newBuilder().setId("CLS").setNamespace(ns1) + .setName("Classification").setRule(AttributeRuleTypeEnum.ATTRIBUTE_RULE_TYPE_ENUM_HIERARCHY) + .setFqn(fqn.toString()).build(); + } else if (key.equals(N2K.getKey())) { return Attribute.newBuilder().setId("N2K").setNamespace(ns1) - .setName("Need to Know").setRule(AttributeRuleTypeEnum.ATTRIBUTE_RULE_TYPE_ENUM_ALL_OF).setFqn(fqn.toString()).build(); - } - else if (key.equals(REL.getKey())) { + .setName("Need to Know").setRule(AttributeRuleTypeEnum.ATTRIBUTE_RULE_TYPE_ENUM_ALL_OF) + .setFqn(fqn.toString()).build(); + } else if (key.equals(REL.getKey())) { return Attribute.newBuilder().setId("REL").setNamespace(ns1) - .setName("Releasable To").setRule(AttributeRuleTypeEnum.ATTRIBUTE_RULE_TYPE_ENUM_ANY_OF).setFqn(fqn.toString()).build(); - } - else if (key.equals(SPECKED.getKey())) { + .setName("Releasable To").setRule(AttributeRuleTypeEnum.ATTRIBUTE_RULE_TYPE_ENUM_ANY_OF) + .setFqn(fqn.toString()).build(); + } else if (key.equals(SPECKED.getKey())) { return Attribute.newBuilder().setId("SPK").setNamespace(ns2) - .setName("specified").setRule(AttributeRuleTypeEnum.ATTRIBUTE_RULE_TYPE_ENUM_ANY_OF).setFqn(fqn.toString()) + .setName("specified").setRule(AttributeRuleTypeEnum.ATTRIBUTE_RULE_TYPE_ENUM_ANY_OF) + .setFqn(fqn.toString()) .addGrants(KeyAccessServer.newBuilder().setUri(SPECIFIED_KAS).build()) .build(); - } - else if (key.equals(UNSPECKED.getKey())) { + } else if (key.equals(UNSPECKED.getKey())) { return Attribute.newBuilder().setId("UNS").setNamespace(ns2) - .setName("unspecified").setRule(AttributeRuleTypeEnum.ATTRIBUTE_RULE_TYPE_ENUM_ANY_OF).setFqn(fqn.toString()).build(); - } - else { + .setName("unspecified").setRule(AttributeRuleTypeEnum.ATTRIBUTE_RULE_TYPE_ENUM_ANY_OF) + .setFqn(fqn.toString()).build(); + } else { return null; } } @@ -202,13 +201,13 @@ private Value mockValueFor(Autoconfigure.AttributeValueFQN fqn) throws AutoConfi Attribute a = mockAttributeFor(an); String v = fqn.value(); Value p = Value.newBuilder() - .setId(a.getId() + ":" + v) - .setAttribute(a) - .setValue(v) - .setFqn(fqn.toString()) - .build(); + .setId(a.getId() + ":" + v) + .setAttribute(a) + .setValue(v) + .setFqn(fqn.toString()) + .build(); - if (an.getKey().equals(N2K.getKey())){ + if (an.getKey().equals(N2K.getKey())) { switch (v.toUpperCase()) { case "INT": p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(KAS_UK).build()).build(); @@ -220,50 +219,46 @@ private Value mockValueFor(Autoconfigure.AttributeValueFQN fqn) throws AutoConfi p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(KAS_US_SA).build()).build(); break; } - } - else if (an.getKey().equals(REL.getKey())) { - switch (v.toUpperCase()) { - case "FVEY": - p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(KAS_AU).build()) - .addGrants(1, KeyAccessServer.newBuilder().setUri(KAS_CA).build()) - .addGrants(1, KeyAccessServer.newBuilder().setUri(KAS_UK).build()) - .addGrants(1, KeyAccessServer.newBuilder().setUri(KAS_NZ).build()) - .addGrants(1, KeyAccessServer.newBuilder().setUri(KAS_US).build()) - .build(); - break; - case "AUS": - p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(KAS_AU).build()) - .build(); - break; - case "CAN": - p = p.toBuilder().addGrants(0, KeyAccessServer.newBuilder().setUri(KAS_CA).build()) - .build(); - break; - case "GBR": - p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(KAS_UK).build()) - .build(); - break; - case "NZL": - p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(KAS_NZ).build()) - .build(); - break; - case "USA": - p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(KAS_US).build()) - .build(); - break; - } - } - else if (an.getKey().equals(CLS.getKey())){ + } else if (an.getKey().equals(REL.getKey())) { + switch (v.toUpperCase()) { + case "FVEY": + p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(KAS_AU).build()) + .addGrants(1, KeyAccessServer.newBuilder().setUri(KAS_CA).build()) + .addGrants(1, KeyAccessServer.newBuilder().setUri(KAS_UK).build()) + .addGrants(1, KeyAccessServer.newBuilder().setUri(KAS_NZ).build()) + .addGrants(1, KeyAccessServer.newBuilder().setUri(KAS_US).build()) + .build(); + break; + case "AUS": + p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(KAS_AU).build()) + .build(); + break; + case "CAN": + p = p.toBuilder().addGrants(0, KeyAccessServer.newBuilder().setUri(KAS_CA).build()) + .build(); + break; + case "GBR": + p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(KAS_UK).build()) + .build(); + break; + case "NZL": + p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(KAS_NZ).build()) + .build(); + break; + case "USA": + p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(KAS_US).build()) + .build(); + break; + } + } else if (an.getKey().equals(CLS.getKey())) { // defaults only - } - else if (an.getKey().equals(SPECKED.getKey())){ - if (fqn.value().toLowerCase().equals("specked")){ + } else if (an.getKey().equals(SPECKED.getKey())) { + if (fqn.value().toLowerCase().equals("specked")) { p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(EVEN_MORE_SPECIFIC_KAS).build()) .build(); } - } - else if (an.getKey().equals(UNSPECKED.getKey())){ - if (fqn.value().toLowerCase().equals("specked")){ + } else if (an.getKey().equals(UNSPECKED.getKey())) { + if (fqn.value().toLowerCase().equals("specked")) { p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(EVEN_MORE_SPECIFIC_KAS).build()) .build(); } @@ -274,11 +269,10 @@ else if (an.getKey().equals(UNSPECKED.getKey())){ @Test public void testAttributeFromURL() throws AutoConfigureException { for (TestCase tc : List.of( - new TestCase("letter", "https://e/attr/a", "https://e", "a"), - new TestCase("number", "https://e/attr/1", "https://e", "1"), - new TestCase("emoji", "https://e/attr/%F0%9F%98%81", "https://e", "😁"), - new TestCase("dash", "https://a-b.com/attr/b-c", "https://a-b.com", "b-c") - )) { + new TestCase("letter", "https://e/attr/a", "https://e", "a"), + new TestCase("number", "https://e/attr/1", "https://e", "1"), + new TestCase("emoji", "https://e/attr/%F0%9F%98%81", "https://e", "😁"), + new TestCase("dash", "https://a-b.com/attr/b-c", "https://a-b.com", "b-c"))) { Autoconfigure.AttributeNameFQN a = new Autoconfigure.AttributeNameFQN(tc.getU()); assertThat(a.authority()).isEqualTo(tc.getAuth()); assertThat(a.name()).isEqualTo(tc.getName()); @@ -288,29 +282,27 @@ public void testAttributeFromURL() throws AutoConfigureException { @Test public void testAttributeFromMalformedURL() { for (TestCase tc : List.of( - new TestCase("no name", "https://e/attr"), - new TestCase("invalid prefix 1", "hxxp://e/attr/a"), - new TestCase("invalid prefix 2", "e/attr/a"), - new TestCase("invalid prefix 3", "file://e/attr/a"), - new TestCase("invalid prefix 4", "https:///attr/a"), - new TestCase("bad encoding", "https://a/attr/%😁"), - new TestCase("with value", "https://e/attr/a/value/b") - )) { + new TestCase("no name", "https://e/attr"), + new TestCase("invalid prefix 1", "hxxp://e/attr/a"), + new TestCase("invalid prefix 2", "e/attr/a"), + new TestCase("invalid prefix 3", "file://e/attr/a"), + new TestCase("invalid prefix 4", "https:///attr/a"), + new TestCase("bad encoding", "https://a/attr/%😁"), + new TestCase("with value", "https://e/attr/a/value/b"))) { assertThatThrownBy(() -> new Autoconfigure.AttributeNameFQN(tc.getU())) - .isInstanceOf(AutoConfigureException.class); + .isInstanceOf(AutoConfigureException.class); } } @Test public void testAttributeValueFromURL() { List testCases = List.of( - new TestCase("number", "https://e/attr/a/value/1", "https://e", "a", "1"), - new TestCase("space", "https://e/attr/a/value/%20", "https://e", "a", " "), - new TestCase("emoji", "https://e/attr/a/value/%F0%9F%98%81", "https://e", "a", "😁"), - new TestCase("numberdef", "https://e/attr/1/value/one", "https://e", "1", "one"), - new TestCase("valuevalue", "https://e/attr/value/value/one", "https://e", "value", "one"), - new TestCase("dash", "https://a-b.com/attr/b-c/value/c-d", "https://a-b.com", "b-c", "c-d") - ); + new TestCase("number", "https://e/attr/a/value/1", "https://e", "a", "1"), + new TestCase("space", "https://e/attr/a/value/%20", "https://e", "a", " "), + new TestCase("emoji", "https://e/attr/a/value/%F0%9F%98%81", "https://e", "a", "😁"), + new TestCase("numberdef", "https://e/attr/1/value/one", "https://e", "1", "one"), + new TestCase("valuevalue", "https://e/attr/value/value/one", "https://e", "value", "one"), + new TestCase("dash", "https://a-b.com/attr/b-c/value/c-d", "https://a-b.com", "b-c", "c-d")); for (TestCase tc : testCases) { assertDoesNotThrow(() -> { @@ -325,29 +317,28 @@ public void testAttributeValueFromURL() { @Test public void testAttributeValueFromMalformedURL() { List testCases = List.of( - new TestCase("no name", "https://e/attr/value/1"), - new TestCase("no value", "https://e/attr/who/value"), - new TestCase("invalid prefix 1", "hxxp://e/attr/a/value/1"), - new TestCase("invalid prefix 2", "e/attr/a/a/value/1"), - new TestCase("bad encoding", "https://a/attr/emoji/value/%😁") - ); + new TestCase("no name", "https://e/attr/value/1"), + new TestCase("no value", "https://e/attr/who/value"), + new TestCase("invalid prefix 1", "hxxp://e/attr/a/value/1"), + new TestCase("invalid prefix 2", "e/attr/a/a/value/1"), + new TestCase("bad encoding", "https://a/attr/emoji/value/%😁")); for (TestCase tc : testCases) { assertThatThrownBy(() -> new AttributeValueFQN(tc.getU())) - .isInstanceOf(AutoConfigureException.class) - .hasMessageContaining("invalid type"); + .isInstanceOf(AutoConfigureException.class) + .hasMessageContaining("invalid type"); } } @Test public void testConfigurationServicePutGet() { List testCases = List.of( - new ConfigurationTestCase("default", List.of(clsA), 1, List.of()), - new ConfigurationTestCase("one-country", List.of(rel2gbr), 1, List.of(KAS_UK)), - new ConfigurationTestCase("two-country", List.of(rel2gbr, rel2nzl), 2, List.of(KAS_UK, KAS_NZ)), - new ConfigurationTestCase("with-default", List.of(clsA, rel2gbr), 2, List.of(KAS_UK)), - new ConfigurationTestCase("need-to-know", List.of(clsTS, rel2usa, n2kSI), 3, List.of(KAS_US, KAS_US_SA)) - ); + new ConfigurationTestCase("default", List.of(clsA), 1, List.of()), + new ConfigurationTestCase("one-country", List.of(rel2gbr), 1, List.of(KAS_UK)), + new ConfigurationTestCase("two-country", List.of(rel2gbr, rel2nzl), 2, List.of(KAS_UK, KAS_NZ)), + new ConfigurationTestCase("with-default", List.of(clsA, rel2gbr), 2, List.of(KAS_UK)), + new ConfigurationTestCase("need-to-know", List.of(clsTS, rel2usa, n2kSI), 3, + List.of(KAS_US, KAS_US_SA))); for (ConfigurationTestCase tc : testCases) { assertDoesNotThrow(() -> { @@ -359,91 +350,85 @@ public void testConfigurationServicePutGet() { Set actualKases = new HashSet<>(); for (Autoconfigure.KeyAccessGrant g : grants.getGrants().values()) { assertThat(g).isNotNull(); - for (String k : g.kases){ + for (String k : g.kases) { actualKases.add(k); } } String[] kasArray = tc.getKases().toArray(new String[tc.getKases().size()]); assertThat(actualKases).containsExactlyInAnyOrder(kasArray); - } - ); + }); } } @Test public void testReasonerConstructAttributeBoolean() { List testCases = List.of( - new ReasonerTestCase( - "one actual with default", - List.of(clsS, rel2can), - List.of(KAS_US), - "https://virtru.com/attr/Classification/value/Secret&https://virtru.com/attr/Releasable%20To/value/CAN", - "[DEFAULT]&(https://kas.ca/)", - "(https://kas.ca/)", - List.of(new KeySplitStep(KAS_CA, "")) - ), - new ReasonerTestCase( - "one defaulted attr", - List.of(clsS), - List.of(KAS_US), - "https://virtru.com/attr/Classification/value/Secret", - "[DEFAULT]", - "", - List.of(new KeySplitStep(KAS_US, "")) - ), - new ReasonerTestCase( - "empty policy", - List.of(), - List.of(KAS_US), - "∅", - "", - "", - List.of(new KeySplitStep(KAS_US, "")) - ), - new ReasonerTestCase( - "old school splits", - List.of(), - List.of(KAS_AU, KAS_CA, KAS_US), - "∅", - "", - "", - List.of(new KeySplitStep(KAS_AU, "1"), new KeySplitStep(KAS_CA, "2"), new KeySplitStep(KAS_US, "3")) - ), - new ReasonerTestCase( - "simple with all three ops", - List.of(clsS, rel2gbr, n2kInt), - List.of(KAS_US), - "https://virtru.com/attr/Classification/value/Secret&https://virtru.com/attr/Releasable%20To/value/GBR&https://virtru.com/attr/Need%20to%20Know/value/INT", - "[DEFAULT]&(https://kas.uk/)&(https://kas.uk/)", - "(https://kas.uk/)", - List.of(new KeySplitStep(KAS_UK, "")) - ), - new ReasonerTestCase( - "compartments", - List.of(clsS, rel2gbr, rel2usa, n2kHCS, n2kSI), - List.of(KAS_US), - "https://virtru.com/attr/Classification/value/Secret&https://virtru.com/attr/Releasable%20To/value/{GBR,USA}&https://virtru.com/attr/Need%20to%20Know/value/{HCS,SI}", - "[DEFAULT]&(https://kas.uk/⋁https://kas.us/)&(https://hcs.kas.us/⋀https://si.kas.us/)", - "(https://kas.uk/⋁https://kas.us/)&(https://hcs.kas.us/)&(https://si.kas.us/)", - List.of(new KeySplitStep(KAS_UK, "1"), new KeySplitStep(KAS_US, "1"), new KeySplitStep(KAS_US_HCS, "2"), new KeySplitStep(KAS_US_SA, "3")) - ), - new ReasonerTestCase( - "compartments - case insensitive", - List.of( - messUpV(clsS), messUpV(rel2gbr), messUpV(rel2usa), messUpV(n2kHCS), messUpV(n2kSI) - ), - List.of(KAS_US), - "https://virtru.com/attr/Classification/value/Secret&https://virtru.com/attr/Releasable%20To/value/{GBR,USA}&https://virtru.com/attr/Need%20to%20Know/value/{HCS,SI}", - "[DEFAULT]&(https://kas.uk/⋁https://kas.us/)&(https://hcs.kas.us/⋀https://si.kas.us/)", - "(https://kas.uk/⋁https://kas.us/)&(https://hcs.kas.us/)&(https://si.kas.us/)", - List.of(new KeySplitStep(KAS_UK, "1"), new KeySplitStep(KAS_US, "1"), new KeySplitStep(KAS_US_HCS, "2"), new KeySplitStep(KAS_US_SA, "3")) - ) - ); + new ReasonerTestCase( + "one actual with default", + List.of(clsS, rel2can), + List.of(KAS_US), + "https://virtru.com/attr/Classification/value/Secret&https://virtru.com/attr/Releasable%20To/value/CAN", + "[DEFAULT]&(https://kas.ca/)", + "(https://kas.ca/)", + List.of(new KeySplitStep(KAS_CA, ""))), + new ReasonerTestCase( + "one defaulted attr", + List.of(clsS), + List.of(KAS_US), + "https://virtru.com/attr/Classification/value/Secret", + "[DEFAULT]", + "", + List.of(new KeySplitStep(KAS_US, ""))), + new ReasonerTestCase( + "empty policy", + List.of(), + List.of(KAS_US), + "∅", + "", + "", + List.of(new KeySplitStep(KAS_US, ""))), + new ReasonerTestCase( + "old school splits", + List.of(), + List.of(KAS_AU, KAS_CA, KAS_US), + "∅", + "", + "", + List.of(new KeySplitStep(KAS_AU, "1"), new KeySplitStep(KAS_CA, "2"), + new KeySplitStep(KAS_US, "3"))), + new ReasonerTestCase( + "simple with all three ops", + List.of(clsS, rel2gbr, n2kInt), + List.of(KAS_US), + "https://virtru.com/attr/Classification/value/Secret&https://virtru.com/attr/Releasable%20To/value/GBR&https://virtru.com/attr/Need%20to%20Know/value/INT", + "[DEFAULT]&(https://kas.uk/)&(https://kas.uk/)", + "(https://kas.uk/)", + List.of(new KeySplitStep(KAS_UK, ""))), + new ReasonerTestCase( + "compartments", + List.of(clsS, rel2gbr, rel2usa, n2kHCS, n2kSI), + List.of(KAS_US), + "https://virtru.com/attr/Classification/value/Secret&https://virtru.com/attr/Releasable%20To/value/{GBR,USA}&https://virtru.com/attr/Need%20to%20Know/value/{HCS,SI}", + "[DEFAULT]&(https://kas.uk/⋁https://kas.us/)&(https://hcs.kas.us/⋀https://si.kas.us/)", + "(https://kas.uk/⋁https://kas.us/)&(https://hcs.kas.us/)&(https://si.kas.us/)", + List.of(new KeySplitStep(KAS_UK, "1"), new KeySplitStep(KAS_US, "1"), + new KeySplitStep(KAS_US_HCS, "2"), new KeySplitStep(KAS_US_SA, "3"))), + new ReasonerTestCase( + "compartments - case insensitive", + List.of( + messUpV(clsS), messUpV(rel2gbr), messUpV(rel2usa), messUpV(n2kHCS), messUpV(n2kSI)), + List.of(KAS_US), + "https://virtru.com/attr/Classification/value/Secret&https://virtru.com/attr/Releasable%20To/value/{GBR,USA}&https://virtru.com/attr/Need%20to%20Know/value/{HCS,SI}", + "[DEFAULT]&(https://kas.uk/⋁https://kas.us/)&(https://hcs.kas.us/⋀https://si.kas.us/)", + "(https://kas.uk/⋁https://kas.us/)&(https://hcs.kas.us/)&(https://si.kas.us/)", + List.of(new KeySplitStep(KAS_UK, "1"), new KeySplitStep(KAS_US, "1"), + new KeySplitStep(KAS_US_HCS, "2"), new KeySplitStep(KAS_US_SA, "3")))); for (ReasonerTestCase tc : testCases) { assertDoesNotThrow(() -> { - Granter reasoner = Autoconfigure.newGranterFromAttributes(valuesToPolicy(tc.getPolicy().toArray(new AttributeValueFQN[0])).toArray(new Value[0])); + Granter reasoner = Autoconfigure.newGranterFromAttributes( + valuesToPolicy(tc.getPolicy().toArray(new AttributeValueFQN[0])).toArray(new Value[0])); assertThat(reasoner).isNotNull(); AttributeBooleanExpression actualAB = reasoner.constructAttributeBoolean(); @@ -455,16 +440,17 @@ public void testReasonerConstructAttributeBoolean() { String reduced = actualKeyed.reduce().toString(); assertThat(reduced).isEqualTo(tc.getReduced()); - var wrapper = new Object(){ int i = 0; }; + var wrapper = new Object() { + int i = 0; + }; List plan = reasoner.plan(tc.getDefaults(), () -> { return String.valueOf(wrapper.i++ + 1); } - + ); assertThat(plan.size()).isEqualTo(tc.getPlan().size()); assertThat(plan).isEqualTo(tc.getPlan()); - } - ); + }); } } @@ -476,15 +462,15 @@ GetAttributeValuesByFqnsResponse getResponse(GetAttributeValuesByFqnsRequest req try { vfqn = new AttributeValueFQN(v); } catch (Exception e) { - return null; // Or throw the exception as needed + return null; // Or throw the exception as needed } Value val = mockValueFor(vfqn); - + builder.putFqnAttributeValues(v, GetAttributeValuesByFqnsResponse.AttributeAndValue.newBuilder() - .setAttribute(val.getAttribute()) - .setValue(val) - .build()); + .setAttribute(val.getAttribute()) + .setValue(val) + .build()); } return builder.build(); @@ -493,96 +479,89 @@ GetAttributeValuesByFqnsResponse getResponse(GetAttributeValuesByFqnsRequest req @Test public void testReasonerSpecificity() { List testCases = List.of( - new ReasonerTestCase( - "uns.uns => default", - List.of(uns2uns), - List.of(KAS_US), - List.of(new KeySplitStep(KAS_US, "")) - ), - new ReasonerTestCase( - "uns.spk => spk", - List.of(uns2spk), - List.of(KAS_US), - List.of(new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, "")) - ), - new ReasonerTestCase( - "spk.uns => spk", - List.of(spk2uns), - List.of(KAS_US), - List.of(new KeySplitStep(SPECIFIED_KAS, "")) - ), - new ReasonerTestCase( - "spk.spk => value.spk", - List.of(spk2spk), - List.of(KAS_US), - List.of(new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, "")) - ), - new ReasonerTestCase( - "spk.spk & spk.uns => value.spk || attr.spk", - List.of(spk2spk, spk2uns), - List.of(KAS_US), - List.of(new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, "1"), new KeySplitStep(SPECIFIED_KAS, "1")) - ), - new ReasonerTestCase( - "spk.uns & spk.spk => value.spk || attr.spk", - List.of(spk2uns, spk2spk), - List.of(KAS_US), - List.of(new KeySplitStep(SPECIFIED_KAS, "1"), new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, "1")) - ), - new ReasonerTestCase( - "uns.spk & spk.spk => value.spk", - List.of(spk2spk, uns2spk), - List.of(KAS_US), - List.of(new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, "")) - ), - new ReasonerTestCase( - "uns.spk & uns.uns => spk", - List.of(uns2spk, uns2uns), - List.of(KAS_US), - List.of(new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, "")) - ), - new ReasonerTestCase( - "uns.uns & uns.spk => spk", - List.of(uns2uns, uns2spk), - List.of(KAS_US), - List.of(new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, "")) - ), - new ReasonerTestCase( - "uns.uns & uns.spk => spk", - List.of(uns2uns, spk2spk), - List.of(KAS_US), - List.of(new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, "")) - ) - ); + new ReasonerTestCase( + "uns.uns => default", + List.of(uns2uns), + List.of(KAS_US), + List.of(new KeySplitStep(KAS_US, ""))), + new ReasonerTestCase( + "uns.spk => spk", + List.of(uns2spk), + List.of(KAS_US), + List.of(new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, ""))), + new ReasonerTestCase( + "spk.uns => spk", + List.of(spk2uns), + List.of(KAS_US), + List.of(new KeySplitStep(SPECIFIED_KAS, ""))), + new ReasonerTestCase( + "spk.spk => value.spk", + List.of(spk2spk), + List.of(KAS_US), + List.of(new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, ""))), + new ReasonerTestCase( + "spk.spk & spk.uns => value.spk || attr.spk", + List.of(spk2spk, spk2uns), + List.of(KAS_US), + List.of(new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, "1"), new KeySplitStep(SPECIFIED_KAS, "1"))), + new ReasonerTestCase( + "spk.uns & spk.spk => value.spk || attr.spk", + List.of(spk2uns, spk2spk), + List.of(KAS_US), + List.of(new KeySplitStep(SPECIFIED_KAS, "1"), new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, "1"))), + new ReasonerTestCase( + "uns.spk & spk.spk => value.spk", + List.of(spk2spk, uns2spk), + List.of(KAS_US), + List.of(new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, ""))), + new ReasonerTestCase( + "uns.spk & uns.uns => spk", + List.of(uns2spk, uns2uns), + List.of(KAS_US), + List.of(new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, ""))), + new ReasonerTestCase( + "uns.uns & uns.spk => spk", + List.of(uns2uns, uns2spk), + List.of(KAS_US), + List.of(new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, ""))), + new ReasonerTestCase( + "uns.uns & uns.spk => spk", + List.of(uns2uns, spk2spk), + List.of(KAS_US), + List.of(new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, "")))); for (ReasonerTestCase tc : testCases) { assertDoesNotThrow(() -> { - AttributesServiceGrpc.AttributesServiceFutureStub attributeGrpcStub = mock(AttributesServiceGrpc.AttributesServiceFutureStub.class); - lenient().when(attributeGrpcStub.getAttributeValuesByFqns(any(GetAttributeValuesByFqnsRequest.class))).thenAnswer( - invocation -> { - GetAttributeValuesByFqnsResponse resp = getResponse((GetAttributeValuesByFqnsRequest) invocation.getArguments()[0]); - SettableFuture future = SettableFuture.create(); - future.set(resp); // Set the request as the future's result - return future; - }); - - Granter reasoner = Autoconfigure.newGranterFromService(attributeGrpcStub, new KASKeyCache(), tc.getPolicy().toArray(new AttributeValueFQN[0])); + AttributesServiceGrpc.AttributesServiceFutureStub attributeGrpcStub = mock( + AttributesServiceGrpc.AttributesServiceFutureStub.class); + lenient().when(attributeGrpcStub.getAttributeValuesByFqns(any(GetAttributeValuesByFqnsRequest.class))) + .thenAnswer( + invocation -> { + GetAttributeValuesByFqnsResponse resp = getResponse( + (GetAttributeValuesByFqnsRequest) invocation.getArguments()[0]); + SettableFuture future = SettableFuture.create(); + future.set(resp); // Set the request as the future's result + return future; + }); + + Granter reasoner = Autoconfigure.newGranterFromService(attributeGrpcStub, new KASKeyCache(), + tc.getPolicy().toArray(new AttributeValueFQN[0])); assertThat(reasoner).isNotNull(); - var wrapper = new Object(){ int i = 0; }; + var wrapper = new Object() { + int i = 0; + }; List plan = reasoner.plan(tc.getDefaults(), () -> { return String.valueOf(wrapper.i++ + 1); } - + ); assertThat(plan.size()).isEqualTo(tc.getPlan().size()); assertThat(plan).hasSameElementsAs(tc.getPlan()); - } - ); + }); } } - private static class TestCase { private final String n; private final String u; @@ -590,7 +569,6 @@ private static class TestCase { private final String name; private final String value; - TestCase(String n, String u, String auth, String name, String value) { this.n = n; this.u = u; @@ -667,7 +645,8 @@ private static class ReasonerTestCase { private final String reduced; private final List plan; - ReasonerTestCase(String name, List policy, List defaults, String ats, String keyed, String reduced, List plan) { + ReasonerTestCase(String name, List policy, List defaults, String ats, String keyed, + String reduced, List plan) { this.name = name; this.policy = policy; this.defaults = defaults; @@ -720,10 +699,9 @@ public List getPlan() { void testStoreKeysToCache_NoKeys() { KASKeyCache keyCache = Mockito.mock(KASKeyCache.class); KeyAccessServer kas1 = KeyAccessServer.newBuilder().setPublicKey( - PublicKey.newBuilder().setCached( - KasPublicKeySet.newBuilder()) - ).build(); - + PublicKey.newBuilder().setCached( + KasPublicKeySet.newBuilder())) + .build(); List kases = List.of(kas1); @@ -739,10 +717,10 @@ void testStoreKeysToCache_WithKeys() { // Create the KasPublicKey object KasPublicKey kasPublicKey1 = KasPublicKey.newBuilder() - .setAlg(KasPublicKeyAlgEnum.KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP256R1) - .setKid("test-kid") - .setPem("public-key-pem") - .build(); + .setAlg(KasPublicKeyAlgEnum.KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP256R1) + .setKid("test-kid") + .setPem("public-key-pem") + .build(); // Add the KasPublicKey to a list List kasPublicKeys = new ArrayList<>(); @@ -750,13 +728,12 @@ void testStoreKeysToCache_WithKeys() { // Create the KeyAccessServer object KeyAccessServer kas1 = KeyAccessServer.newBuilder() - .setPublicKey(PublicKey.newBuilder() - .setCached(KasPublicKeySet.newBuilder() - .addAllKeys(kasPublicKeys) - .build()) - ) - .setUri("https://example.com/kas") - .build(); + .setPublicKey(PublicKey.newBuilder() + .setCached(KasPublicKeySet.newBuilder() + .addAllKeys(kasPublicKeys) + .build())) + .setUri("https://example.com/kas") + .build(); // Add the KeyAccessServer to a list List kases = List.of(kas1); @@ -780,15 +757,15 @@ void testStoreKeysToCache_MultipleKasEntries() { // Create the KasPublicKey object KasPublicKey kasPublicKey1 = KasPublicKey.newBuilder() - .setAlg(KasPublicKeyAlgEnum.KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP256R1) - .setKid("test-kid") - .setPem("public-key-pem") - .build(); + .setAlg(KasPublicKeyAlgEnum.KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP256R1) + .setKid("test-kid") + .setPem("public-key-pem") + .build(); KasPublicKey kasPublicKey2 = KasPublicKey.newBuilder() - .setAlg(KasPublicKeyAlgEnum.KAS_PUBLIC_KEY_ALG_ENUM_RSA_2048) - .setKid("test-kid-2") - .setPem("public-key-pem-2") - .build(); + .setAlg(KasPublicKeyAlgEnum.KAS_PUBLIC_KEY_ALG_ENUM_RSA_2048) + .setKid("test-kid-2") + .setPem("public-key-pem-2") + .build(); // Add the KasPublicKey to a list List kasPublicKeys = new ArrayList<>(); @@ -797,13 +774,12 @@ void testStoreKeysToCache_MultipleKasEntries() { // Create the KeyAccessServer object KeyAccessServer kas1 = KeyAccessServer.newBuilder() - .setPublicKey(PublicKey.newBuilder() - .setCached(KasPublicKeySet.newBuilder() - .addAllKeys(kasPublicKeys) - .build()) - ) - .setUri("https://example.com/kas") - .build(); + .setPublicKey(PublicKey.newBuilder() + .setCached(KasPublicKeySet.newBuilder() + .addAllKeys(kasPublicKeys) + .build())) + .setUri("https://example.com/kas") + .build(); // Add the KeyAccessServer to a list List kases = List.of(kas1); @@ -827,8 +803,8 @@ void testStoreKeysToCache_MultipleKasEntries() { assertEquals("public-key-pem-2", storedKASInfo2.PublicKey); } - - GetAttributeValuesByFqnsResponse getResponseWithGrants(GetAttributeValuesByFqnsRequest req, List grants) { + GetAttributeValuesByFqnsResponse getResponseWithGrants(GetAttributeValuesByFqnsRequest req, + List grants) { GetAttributeValuesByFqnsResponse.Builder builder = GetAttributeValuesByFqnsResponse.newBuilder(); for (String v : req.getFqnsList()) { @@ -836,34 +812,33 @@ GetAttributeValuesByFqnsResponse getResponseWithGrants(GetAttributeValuesByFqnsR try { vfqn = new AttributeValueFQN(v); } catch (Exception e) { - return null; // Or throw the exception as needed + return null; // Or throw the exception as needed } Value val = Value.newBuilder(mockValueFor(vfqn)).addAllGrants(grants).build(); - + builder.putFqnAttributeValues(v, GetAttributeValuesByFqnsResponse.AttributeAndValue.newBuilder() - .setAttribute(val.getAttribute()) - .setValue(val) - .build()); + .setAttribute(val.getAttribute()) + .setValue(val) + .build()); } return builder.build(); } - @Test void testKeyCacheFromGrants() throws InterruptedException, ExecutionException { // Create the KasPublicKey object KasPublicKey kasPublicKey1 = KasPublicKey.newBuilder() - .setAlg(KasPublicKeyAlgEnum.KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP256R1) - .setKid("test-kid") - .setPem("public-key-pem") - .build(); + .setAlg(KasPublicKeyAlgEnum.KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP256R1) + .setKid("test-kid") + .setPem("public-key-pem") + .build(); KasPublicKey kasPublicKey2 = KasPublicKey.newBuilder() - .setAlg(KasPublicKeyAlgEnum.KAS_PUBLIC_KEY_ALG_ENUM_RSA_2048) - .setKid("test-kid-2") - .setPem("public-key-pem-2") - .build(); + .setAlg(KasPublicKeyAlgEnum.KAS_PUBLIC_KEY_ALG_ENUM_RSA_2048) + .setKid("test-kid-2") + .setPem("public-key-pem-2") + .build(); // Add the KasPublicKey to a list List kasPublicKeys = new ArrayList<>(); @@ -872,26 +847,29 @@ void testKeyCacheFromGrants() throws InterruptedException, ExecutionException { // Create the KeyAccessServer object KeyAccessServer kas1 = KeyAccessServer.newBuilder() - .setPublicKey(PublicKey.newBuilder() - .setCached(KasPublicKeySet.newBuilder() - .addAllKeys(kasPublicKeys) - .build()) - ) - .setUri("https://example.com/kas") - .build(); - - AttributesServiceGrpc.AttributesServiceFutureStub attributeGrpcStub = mock(AttributesServiceGrpc.AttributesServiceFutureStub.class); - lenient().when(attributeGrpcStub.getAttributeValuesByFqns(any(GetAttributeValuesByFqnsRequest.class))).thenAnswer( - invocation -> { - GetAttributeValuesByFqnsResponse resp = getResponseWithGrants((GetAttributeValuesByFqnsRequest) invocation.getArguments()[0], List.of(kas1)); - SettableFuture future = SettableFuture.create(); - future.set(resp); // Set the request as the future's result - return future; - }); + .setPublicKey(PublicKey.newBuilder() + .setCached(KasPublicKeySet.newBuilder() + .addAllKeys(kasPublicKeys) + .build())) + .setUri("https://example.com/kas") + .build(); + + AttributesServiceGrpc.AttributesServiceFutureStub attributeGrpcStub = mock( + AttributesServiceGrpc.AttributesServiceFutureStub.class); + lenient().when(attributeGrpcStub.getAttributeValuesByFqns(any(GetAttributeValuesByFqnsRequest.class))) + .thenAnswer( + invocation -> { + GetAttributeValuesByFqnsResponse resp = getResponseWithGrants( + (GetAttributeValuesByFqnsRequest) invocation.getArguments()[0], List.of(kas1)); + SettableFuture future = SettableFuture.create(); + future.set(resp); // Set the request as the future's result + return future; + }); KASKeyCache keyCache = new KASKeyCache(); - Granter reasoner = Autoconfigure.newGranterFromService(attributeGrpcStub, keyCache, List.of(clsS, rel2gbr, rel2usa, n2kHCS, n2kSI).toArray(new AttributeValueFQN[0])); + Granter reasoner = Autoconfigure.newGranterFromService(attributeGrpcStub, keyCache, + List.of(clsS, rel2gbr, rel2usa, n2kHCS, n2kSI).toArray(new AttributeValueFQN[0])); assertThat(reasoner).isNotNull(); // Verify that the key was stored in the cache @@ -911,5 +889,4 @@ void testKeyCacheFromGrants() throws InterruptedException, ExecutionException { } - } diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/KASClientTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/KASClientTest.java index 8f816548..f44bc564 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/KASClientTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/KASClientTest.java @@ -57,7 +57,8 @@ public void publicKey(PublicKeyRequest request, StreamObserver respons var gson = new Gson(); var req = gson.fromJson(requestBodyJson, KASClient.RewrapRequestBody.class); - var decryptedKey = new AsymDecryption(serverKeypair.getPrivate()).decrypt(Base64.getDecoder().decode(req.keyAccess.wrappedKey)); + var decryptedKey = new AsymDecryption(serverKeypair.getPrivate()) + .decrypt(Base64.getDecoder().decode(req.keyAccess.wrappedKey)); var encryptedKey = new AsymEncryption(req.clientPublicKey).encrypt(decryptedKey); - responseObserver.onNext(RewrapResponse.newBuilder().setEntityWrappedKey(ByteString.copyFrom(encryptedKey)).build()); + responseObserver.onNext( + RewrapResponse.newBuilder().setEntityWrappedKey(ByteString.copyFrom(encryptedKey)).build()); responseObserver.onCompleted(); } }; @@ -174,7 +179,8 @@ public void rewrap(RewrapRequest request, StreamObserver respons public void testAddressNormalization() { var lastAddress = new AtomicReference(); var dpopKeypair = CryptoUtils.generateRSAKeypair(); - var dpopKey = new RSAKey.Builder((RSAPublicKey)dpopKeypair.getPublic()).privateKey(dpopKeypair.getPrivate()).build(); + var dpopKey = new RSAKey.Builder((RSAPublicKey) dpopKeypair.getPublic()).privateKey(dpopKeypair.getPrivate()) + .build(); var kasClient = new KASClient(addr -> { lastAddress.set(addr); return ManagedChannelBuilder.forTarget(addr).build(); diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/SDKBuilderTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/SDKBuilderTest.java index e35c7385..0a85a181 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/SDKBuilderTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/SDKBuilderTest.java @@ -45,10 +45,9 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; - public class SDKBuilderTest { - final String EXAMPLE_COM_PEM= "-----BEGIN CERTIFICATE-----\n" + + final String EXAMPLE_COM_PEM = "-----BEGIN CERTIFICATE-----\n" + "MIIBqTCCARKgAwIBAgIIT0xFd/5uogEwDQYJKoZIhvcNAQEFBQAwFjEUMBIGA1UEAxMLZXhhbXBs\n" + "ZS5jb20wIBcNMTcwMTIwMTczOTIwWhgPOTk5OTEyMzEyMzU5NTlaMBYxFDASBgNVBAMTC2V4YW1w\n" + "bGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC2Tl2MdaUFmjAaYwmEwgEVRfVqwJO4\n" + @@ -57,45 +56,46 @@ public class SDKBuilderTest { "CSqGSIb3DQEBBQUAA4GBAGfw0xavZSJXxuFAwxCZBtne9BAtk+SmfKkTI21v8Tx6w/p5Yt0IIvF3\n" + "0wCES7YVZ+zUc8vtVVyk1q3f1ZqXqVvzRCjzLzQnu6VVLBaiZPH9SYNX6j0pHhBvx1ZUMopJPr2D\n" + "avTXCTSHY5JoX20KEwfu8QQXQRDUzyc0QKn9SiE3\n" + - "-----END CERTIFICATE-----"; + "-----END CERTIFICATE-----"; + @Test - void testDirCertsSSLContext() throws Exception{ + void testDirCertsSSLContext() throws Exception { Path certDirPath = Files.createTempDirectory("certs"); File pemFile = new File(certDirPath.toAbsolutePath().toString(), "ca.pem"); FileOutputStream fos = new FileOutputStream(pemFile); - IOUtils.write(EXAMPLE_COM_PEM,fos); + IOUtils.write(EXAMPLE_COM_PEM, fos); fos.close(); SDKBuilder builder = SDKBuilder.newBuilder().sslFactoryFromDirectory(certDirPath.toAbsolutePath().toString()); SSLFactory sslFactory = builder.getSslFactory(); assertNotNull(sslFactory); - X509Certificate[] acceptedIssuers= sslFactory.getTrustManager().get().getAcceptedIssuers(); - assertEquals(1, Arrays.stream(acceptedIssuers).filter(x->x.getIssuerX500Principal().getName() + X509Certificate[] acceptedIssuers = sslFactory.getTrustManager().get().getAcceptedIssuers(); + assertEquals(1, Arrays.stream(acceptedIssuers).filter(x -> x.getIssuerX500Principal().getName() .equals("CN=example.com")).count()); } @Test - void testKeystoreSSLContext() throws Exception{ + void testKeystoreSSLContext() throws Exception { KeyStore keystore = KeyStoreUtils.createKeyStore(); keystore.setCertificateEntry("example.com", PemUtils.parseCertificate(EXAMPLE_COM_PEM).get(0)); Path keyStorePath = Files.createTempFile("ca", "jks"); keystore.store(new FileOutputStream(keyStorePath.toAbsolutePath().toString()), "foo".toCharArray()); - SDKBuilder builder = SDKBuilder.newBuilder().sslFactoryFromKeyStore(keyStorePath.toAbsolutePath().toString(), "foo"); + SDKBuilder builder = SDKBuilder.newBuilder().sslFactoryFromKeyStore(keyStorePath.toAbsolutePath().toString(), + "foo"); SSLFactory sslFactory = builder.getSslFactory(); assertNotNull(sslFactory); - X509Certificate[] acceptedIssuers= sslFactory.getTrustManager().get().getAcceptedIssuers(); - assertEquals(1, Arrays.stream(acceptedIssuers).filter(x->x.getIssuerX500Principal().getName() + X509Certificate[] acceptedIssuers = sslFactory.getTrustManager().get().getAcceptedIssuers(); + assertEquals(1, Arrays.stream(acceptedIssuers).filter(x -> x.getIssuerX500Principal().getName() .equals("CN=example.com")).count()); } - @Test - public void testPlatformPlainTextAndIDPWithSSL() throws Exception{ + public void testPlatformPlainTextAndIDPWithSSL() throws Exception { sdkServicesSetup(false, true); } @Test - void testSDKServicesWithTruststore() throws Exception{ + void testSDKServicesWithTruststore() throws Exception { sdkServicesSetup(true, true); } @@ -104,7 +104,7 @@ void testCreatingSDKServicesPlainText() throws Exception { sdkServicesSetup(false, false); } - void sdkServicesSetup(boolean useSSLPlatform, boolean useSSLIDP) throws Exception{ + void sdkServicesSetup(boolean useSSLPlatform, boolean useSSLIDP) throws Exception { HeldCertificate rootCertificate = new HeldCertificate.Builder() .certificateAuthority(0) @@ -125,9 +125,10 @@ void sdkServicesSetup(boolean useSSLPlatform, boolean useSSLIDP) throws Exceptio SDK.Services services = null; // we use the HTTP server for two things: // * it returns the OIDC configuration we use at bootstrapping time - // * it fakes out being an IDP and returns an access token when need to retrieve an access token + // * it fakes out being an IDP and returns an access token when need to retrieve + // an access token try (MockWebServer httpServer = new MockWebServer()) { - if (useSSLIDP){ + if (useSSLIDP) { httpServer.useHttps(serverHandshakeCertificates.sslSocketFactory(), false); } String oidcConfig; @@ -138,18 +139,21 @@ void sdkServicesSetup(boolean useSSLPlatform, boolean useSSLIDP) throws Exceptio oidcConfig = oidcConfig // if we don't do this then the library code complains that the issuer is wrong .replace("", issuer) - // we want this server to be called when we fetch an access token during a service call + // we want this server to be called when we fetch an access token during a + // service call .replace("", httpServer.url("tokens").toString()); httpServer.enqueue(new MockResponse() .setBody(oidcConfig) - .setHeader("Content-type", "application/json") - ); + .setHeader("Content-type", "application/json")); - // this service returns the platform_issuer url to the SDK during bootstrapping. This - // tells the SDK where to download the OIDC discovery document from (our test webserver!) + // this service returns the platform_issuer url to the SDK during bootstrapping. + // This + // tells the SDK where to download the OIDC discovery document from (our test + // webserver!) WellKnownServiceGrpc.WellKnownServiceImplBase wellKnownService = new WellKnownServiceGrpc.WellKnownServiceImplBase() { @Override - public void getWellKnownConfiguration(GetWellKnownConfigurationRequest request, StreamObserver responseObserver) { + public void getWellKnownConfiguration(GetWellKnownConfigurationRequest request, + StreamObserver responseObserver) { var val = Value.newBuilder().setStringValue(issuer).build(); var config = Struct.newBuilder().putFields("platform_issuer", val).build(); var response = GetWellKnownConfigurationResponse @@ -161,31 +165,38 @@ public void getWellKnownConfiguration(GetWellKnownConfigurationRequest request, } }; - // remember the auth headers that we received during GRPC calls to platform services + // remember the auth headers that we received during GRPC calls to platform + // services AtomicReference servicesAuthHeader = new AtomicReference<>(null); AtomicReference servicesDPoPHeader = new AtomicReference<>(null); // remember the auth headers that we received during GRPC calls to KAS AtomicReference kasAuthHeader = new AtomicReference<>(null); AtomicReference kasDPoPHeader = new AtomicReference<>(null); - // we use the server in two different ways. the first time we use it to actually return - // issuer for bootstrapping. the second time we use the interception functionality in order + // we use the server in two different ways. the first time we use it to actually + // return + // issuer for bootstrapping. the second time we use the interception + // functionality in order // to make sure that we are including a DPoP proof and an auth header ServerBuilder platformServicesServerBuilder = ServerBuilder .forPort(getRandomPort()) .directExecutor() .addService(wellKnownService) - .addService(new NamespaceServiceGrpc.NamespaceServiceImplBase() {}) + .addService(new NamespaceServiceGrpc.NamespaceServiceImplBase() { + }) .intercept(new ServerInterceptor() { @Override - public ServerCall.Listener interceptCall(ServerCall call, Metadata headers, ServerCallHandler next) { - servicesAuthHeader.set(headers.get(Metadata.Key.of("Authorization", Metadata.ASCII_STRING_MARSHALLER))); - servicesDPoPHeader.set(headers.get(Metadata.Key.of("DPoP", Metadata.ASCII_STRING_MARSHALLER))); + public ServerCall.Listener interceptCall(ServerCall call, + Metadata headers, ServerCallHandler next) { + servicesAuthHeader.set( + headers.get(Metadata.Key.of("Authorization", Metadata.ASCII_STRING_MARSHALLER))); + servicesDPoPHeader + .set(headers.get(Metadata.Key.of("DPoP", Metadata.ASCII_STRING_MARSHALLER))); return next.startCall(call, headers); } }); - if (useSSLPlatform){ - platformServicesServerBuilder = platformServicesServerBuilder.useTransportSecurity( + if (useSSLPlatform) { + platformServicesServerBuilder = platformServicesServerBuilder.useTransportSecurity( new ByteArrayInputStream(serverCertificate.certificatePem().getBytes()), new ByteArrayInputStream(serverCertificate.privateKeyPkcs8Pem().getBytes())); } @@ -205,14 +216,16 @@ public void rewrap(RewrapRequest request, StreamObserver respons }) .intercept(new ServerInterceptor() { @Override - public ServerCall.Listener interceptCall(ServerCall call, Metadata headers, ServerCallHandler next) { - kasAuthHeader.set(headers.get(Metadata.Key.of("Authorization", Metadata.ASCII_STRING_MARSHALLER))); + public ServerCall.Listener interceptCall(ServerCall call, + Metadata headers, ServerCallHandler next) { + kasAuthHeader.set( + headers.get(Metadata.Key.of("Authorization", Metadata.ASCII_STRING_MARSHALLER))); kasDPoPHeader.set(headers.get(Metadata.Key.of("DPoP", Metadata.ASCII_STRING_MARSHALLER))); return next.startCall(call, headers); } }); - if(useSSLPlatform){ + if (useSSLPlatform) { kasServerBuilder = kasServerBuilder.useTransportSecurity( new ByteArrayInputStream(serverCertificate.certificatePem().getBytes()), new ByteArrayInputStream(serverCertificate.privateKeyPkcs8Pem().getBytes())); @@ -225,12 +238,12 @@ public ServerCall.Listener interceptCall(ServerCall ServerCall.Listener interceptCall(ServerCall ServerCall.Listener interceptCall(ServerCall ServerCall.Listener interceptCall(ServerCall ServerCall.Listener interceptCall(ServerCall responseObserver) { + public void getWellKnownConfiguration(GetWellKnownConfigurationRequest request, + StreamObserver responseObserver) { // don't return a platform issuer responseObserver.onNext(GetWellKnownConfigurationResponse.getDefaultInstance()); responseObserver.onCompleted(); @@ -332,17 +353,18 @@ public void getWellKnownConfiguration(GetWellKnownConfigurationRequest request, .directExecutor() .intercept(new ServerInterceptor() { @Override - public ServerCall.Listener interceptCall(ServerCall call, Metadata headers, ServerCallHandler next) { + public ServerCall.Listener interceptCall(ServerCall call, + Metadata headers, ServerCallHandler next) { authHeader.set( - headers.get(Metadata.Key.of("Authorization", Metadata.ASCII_STRING_MARSHALLER)) - ); + headers.get(Metadata.Key.of("Authorization", Metadata.ASCII_STRING_MARSHALLER))); return next.startCall(call, headers); } }) .addService(wellKnownService) .addService(new NamespaceServiceGrpc.NamespaceServiceImplBase() { @Override - public void getNamespace(GetNamespaceRequest request, StreamObserver responseObserver) { + public void getNamespace(GetNamespaceRequest request, + StreamObserver responseObserver) { getNsCalled.set(true); responseObserver.onNext(GetNamespaceResponse.getDefaultInstance()); responseObserver.onCompleted(); @@ -361,7 +383,6 @@ public void getNamespace(GetNamespaceRequest request, StreamObserver keypairs = new ArrayList<>(); @BeforeAll @@ -105,7 +98,8 @@ void testSimpleTDFEncryptAndDecrypt() throws Exception { ListenableFuture resp1 = mock(ListenableFuture.class); lenient().when(resp1.get()).thenReturn(GetAttributeValuesByFqnsResponse.newBuilder().build()); - lenient().when(attributeGrpcStub.getAttributeValuesByFqns(any(GetAttributeValuesByFqnsRequest.class))).thenReturn(resp1); + lenient().when(attributeGrpcStub.getAttributeValuesByFqns(any(GetAttributeValuesByFqnsRequest.class))) + .thenReturn(resp1); SecureRandom secureRandom = new SecureRandom(); byte[] key = new byte[32]; @@ -126,8 +120,7 @@ void testSimpleTDFEncryptAndDecrypt() throws Exception { Config.withAutoconfigure(false), Config.withKasInformation(getKASInfos()), Config.withMetaData("here is some metadata"), - Config.withAssertionConfig(assertion1) - ); + Config.withAssertionConfig(assertion1)); String plainText = "this is extremely sensitive stuff!!!"; InputStream plainTextInputStream = new ByteArrayInputStream(plainText.getBytes()); @@ -137,10 +130,12 @@ void testSimpleTDFEncryptAndDecrypt() throws Exception { tdf.createTDF(plainTextInputStream, tdfOutputStream, config, kas, attributeGrpcStub); var assertionVerificationKeys = new Config.AssertionVerificationKeys(); - assertionVerificationKeys.defaultKey = new AssertionConfig.AssertionKey(AssertionConfig.AssertionKeyAlg.HS256, key); + assertionVerificationKeys.defaultKey = new AssertionConfig.AssertionKey(AssertionConfig.AssertionKeyAlg.HS256, + key); var unwrappedData = new ByteArrayOutputStream(); - var reader = tdf.loadTDF(new SeekableInMemoryByteChannel(tdfOutputStream.toByteArray()), kas, assertionVerificationKeys); + var reader = tdf.loadTDF(new SeekableInMemoryByteChannel(tdfOutputStream.toByteArray()), kas, + assertionVerificationKeys); assertThat(reader.getManifest().payload.mimeType).isEqualTo("application/octet-stream"); reader.readPayload(unwrappedData); @@ -156,7 +151,8 @@ void testSimpleTDFWithAssertionWithRS256() throws Exception { ListenableFuture resp1 = mock(ListenableFuture.class); lenient().when(resp1.get()).thenReturn(GetAttributeValuesByFqnsResponse.newBuilder().build()); - lenient().when(attributeGrpcStub.getAttributeValuesByFqns(any(GetAttributeValuesByFqnsRequest.class))).thenReturn(resp1); + lenient().when(attributeGrpcStub.getAttributeValuesByFqns(any(GetAttributeValuesByFqnsRequest.class))) + .thenReturn(resp1); String assertion1Id = "assertion1"; var keypair = CryptoUtils.generateRSAKeypair(); @@ -175,8 +171,7 @@ void testSimpleTDFWithAssertionWithRS256() throws Exception { Config.TDFConfig config = Config.newTDFConfig( Config.withAutoconfigure(false), Config.withKasInformation(getKASInfos()), - Config.withAssertionConfig(assertionConfig) - ); + Config.withAssertionConfig(assertionConfig)); String plainText = "this is extremely sensitive stuff!!!"; InputStream plainTextInputStream = new ByteArrayInputStream(plainText.getBytes()); @@ -190,7 +185,8 @@ void testSimpleTDFWithAssertionWithRS256() throws Exception { new AssertionConfig.AssertionKey(AssertionConfig.AssertionKeyAlg.RS256, keypair.getPublic())); var unwrappedData = new ByteArrayOutputStream(); - var reader = tdf.loadTDF(new SeekableInMemoryByteChannel(tdfOutputStream.toByteArray()), kas, assertionVerificationKeys); + var reader = tdf.loadTDF(new SeekableInMemoryByteChannel(tdfOutputStream.toByteArray()), kas, + assertionVerificationKeys); reader.readPayload(unwrappedData); assertThat(unwrappedData.toString(StandardCharsets.UTF_8)) @@ -203,7 +199,8 @@ void testSimpleTDFWithAssertionWithHS256() throws Exception { ListenableFuture resp1 = mock(ListenableFuture.class); lenient().when(resp1.get()).thenReturn(GetAttributeValuesByFqnsResponse.newBuilder().build()); - lenient().when(attributeGrpcStub.getAttributeValuesByFqns(any(GetAttributeValuesByFqnsRequest.class))).thenReturn(resp1); + lenient().when(attributeGrpcStub.getAttributeValuesByFqns(any(GetAttributeValuesByFqnsRequest.class))) + .thenReturn(resp1); String assertion1Id = "assertion1"; var assertionConfig1 = new AssertionConfig(); @@ -230,8 +227,7 @@ void testSimpleTDFWithAssertionWithHS256() throws Exception { Config.TDFConfig config = Config.newTDFConfig( Config.withAutoconfigure(false), Config.withKasInformation(getKASInfos()), - Config.withAssertionConfig(assertionConfig1, assertionConfig2) - ); + Config.withAssertionConfig(assertionConfig1, assertionConfig2)); String plainText = "this is extremely sensitive stuff!!!"; InputStream plainTextInputStream = new ByteArrayInputStream(plainText.getBytes()); @@ -260,7 +256,8 @@ void testSimpleTDFWithAssertionWithHS256() throws Exception { } else if (assertion.id.equals(assertion2Id)) { assertThat(assertion.statement.format).isEqualTo("json"); assertThat(assertion.statement.schema).isEqualTo("urn:nato:stanag:5636:A:1:elements:json"); - assertThat(assertion.statement.value).isEqualTo("{\"uuid\":\"f74efb60-4a9a-11ef-a6f1-8ee1a61c148a\",\"body\":{\"dataAttributes\":null,\"dissem\":null}}"); + assertThat(assertion.statement.value).isEqualTo( + "{\"uuid\":\"f74efb60-4a9a-11ef-a6f1-8ee1a61c148a\",\"body\":{\"dataAttributes\":null,\"dissem\":null}}"); assertThat(assertion.type).isEqualTo(AssertionConfig.Type.HandlingAssertion.toString()); } else { throw new RuntimeException("unexpected assertion id: " + assertion.id); @@ -273,7 +270,8 @@ public void testCreatingTDFWithMultipleSegments() throws Exception { ListenableFuture resp1 = mock(ListenableFuture.class); lenient().when(resp1.get()).thenReturn(GetAttributeValuesByFqnsResponse.newBuilder().build()); - lenient().when(attributeGrpcStub.getAttributeValuesByFqns(any(GetAttributeValuesByFqnsRequest.class))).thenReturn(resp1); + lenient().when(attributeGrpcStub.getAttributeValuesByFqns(any(GetAttributeValuesByFqnsRequest.class))) + .thenReturn(resp1); var random = new Random(); @@ -281,8 +279,7 @@ public void testCreatingTDFWithMultipleSegments() throws Exception { Config.withAutoconfigure(false), Config.withKasInformation(getKASInfos()), // use a random segment size that makes sure that we will use multiple segments - Config.withSegmentSize(1 + random.nextInt(20)) - ); + Config.withSegmentSize(1 + random.nextInt(20))); // data should be bigger than the largest segment var data = new byte[21 + random.nextInt(2048)]; @@ -305,8 +302,8 @@ public void testCreatingTDFWithMultipleSegments() throws Exception { public void testCreatingTooLargeTDF() throws Exception { ListenableFuture resp1 = mock(ListenableFuture.class); lenient().when(resp1.get()).thenReturn(GetAttributeValuesByFqnsResponse.newBuilder().build()); - lenient().when(attributeGrpcStub.getAttributeValuesByFqns(any(GetAttributeValuesByFqnsRequest.class))).thenReturn(resp1); - + lenient().when(attributeGrpcStub.getAttributeValuesByFqns(any(GetAttributeValuesByFqnsRequest.class))) + .thenReturn(resp1); var random = new Random(); var maxSize = random.nextInt(1024); @@ -333,9 +330,12 @@ public int read(byte[] b, int off, int len) { var os = new OutputStream() { @Override - public void write(int b) {} + public void write(int b) { + } + @Override - public void write(byte[] b, int off, int len) {} + public void write(byte[] b, int off, int len) { + } }; var tdf = new TDF(maxSize); @@ -356,15 +356,15 @@ public void testCreateTDFWithMimeType() throws Exception { ListenableFuture resp1 = mock(ListenableFuture.class); lenient().when(resp1.get()).thenReturn(GetAttributeValuesByFqnsResponse.newBuilder().build()); - lenient().when(attributeGrpcStub.getAttributeValuesByFqns(any(GetAttributeValuesByFqnsRequest.class))).thenReturn(resp1); + lenient().when(attributeGrpcStub.getAttributeValuesByFqns(any(GetAttributeValuesByFqnsRequest.class))) + .thenReturn(resp1); final String mimeType = "application/pdf"; Config.TDFConfig config = Config.newTDFConfig( Config.withAutoconfigure(false), Config.withKasInformation(getKASInfos()), - Config.withMimeType(mimeType) - ); + Config.withMimeType(mimeType)); String plainText = "this is extremely sensitive stuff!!!"; InputStream plainTextInputStream = new ByteArrayInputStream(plainText.getBytes()); diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/nanotdf/HeaderTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/nanotdf/HeaderTest.java index 649efc6a..51ca7f66 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/nanotdf/HeaderTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/nanotdf/HeaderTest.java @@ -18,7 +18,7 @@ void setUp() { @Test void settingAndGettingMagicNumberAndVersion() { byte[] expected = header.getMagicNumberAndVersion(); - assertArrayEquals(expected, new byte[]{0x4C, 0x31, 0x4C}); + assertArrayEquals(expected, new byte[] { 0x4C, 0x31, 0x4C }); } @Test @@ -47,7 +47,7 @@ void settingAndGettingEphemeralKey() { ECCMode mode = new ECCMode((byte) 1); // Initialize the ECCMode object header.setECCMode(mode); // Set the ECCMode object - int keySize = mode.getECCompressedPubKeySize(mode.getEllipticCurveType()); + int keySize = ECCMode.getECCompressedPubKeySize(mode.getEllipticCurveType()); byte[] key = new byte[keySize]; // Ensure the key size is correct header.setEphemeralKey(key); assertArrayEquals(key, header.getEphemeralKey()); @@ -55,7 +55,7 @@ void settingAndGettingEphemeralKey() { @Test void settingEphemeralKeyWithInvalidSize() { - byte[] key = new byte[]{1, 2, 3}; + byte[] key = new byte[] { 1, 2, 3 }; header.setECCMode(new ECCMode((byte) 1)); // ECC mode with key size > 3 assertThrows(IllegalArgumentException.class, () -> header.setEphemeralKey(key)); } diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/nanotdf/NanoTDFHeaderTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/nanotdf/NanoTDFHeaderTest.java index a68bf06a..ae289128 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/nanotdf/NanoTDFHeaderTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/nanotdf/NanoTDFHeaderTest.java @@ -11,9 +11,6 @@ import java.io.*; import java.nio.ByteBuffer; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; import java.security.*; import java.security.cert.CertificateException; import java.security.spec.InvalidKeySpecException; @@ -21,227 +18,259 @@ public class NanoTDFHeaderTest { - byte[] binding = new byte[]{(byte) 0x33, (byte) 0x31, (byte) 0x63, (byte) 0x31, - (byte) 0x66, (byte) 0x35, (byte) 0x35, (byte) 0x00}; - String kasUrl = "https://api.example.com/kas"; - String remotePolicyUrl = "https://api-develop01.develop.virtru.com/acm/api/policies/1a1d5e42-bf91-45c7-a86a-61d5331c1f55"; - - // Curve - "prime256v1" - String sdkPrivateKey = "-----BEGIN PRIVATE KEY-----\n" + - "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg1HjFYV8D16BQszNW\n" + - "6Hx/JxTE53oqk5/bWaIj4qV5tOyhRANCAAQW1Hsq0tzxN6ObuXqV+JoJN0f78Em/\n" + - "PpJXUV02Y6Ex3WlxK/Oaebj8ATsbfaPaxrhyCWB3nc3w/W6+lySlLPn5\n" + - "-----END PRIVATE KEY-----"; - - String sdkPublicKey = "-----BEGIN PUBLIC KEY-----\n" + - "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFtR7KtLc8Tejm7l6lfiaCTdH+/BJ\n" + - "vz6SV1FdNmOhMd1pcSvzmnm4/AE7G32j2sa4cglgd53N8P1uvpckpSz5+Q==\n" + - "-----END PUBLIC KEY-----"; - - String kasPrivateKey = "-----BEGIN PRIVATE KEY-----\n" + - "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgu2Hmm80uUzQB1OfB\n" + - "PyMhWIyJhPA61v+j0arvcLjTwtqhRANCAASHCLUHY4szFiVV++C9+AFMkEL2gG+O\n" + - "byN4Hi7Ywl8GMPOAPcQdIeUkoTd9vub9PcuSj23I8/pLVzs23qhefoUf\n" + - "-----END PRIVATE KEY-----"; - - String kasPublicKey = "-----BEGIN PUBLIC KEY-----\n" + - "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEhwi1B2OLMxYlVfvgvfgBTJBC9oBv\n" + - "jm8jeB4u2MJfBjDzgD3EHSHlJKE3fb7m/T3Lko9tyPP6S1c7Nt6oXn6FHw==\n" + - "-----END PUBLIC KEY-----"; - - byte[] compressedPubKey = new byte[]{ - (byte) 0x03, (byte) 0x16, (byte) 0xd4, (byte) 0x7b, (byte) 0x2a, (byte) 0xd2, (byte) 0xdc, (byte) 0xf1, - (byte) 0x37, (byte) 0xa3, (byte) 0x9b, (byte) 0xb9, (byte) 0x7a, (byte) 0x95, (byte) 0xf8, (byte) 0x9a, - (byte) 0x09, (byte) 0x37, (byte) 0x47, (byte) 0xfb, (byte) 0xf0, (byte) 0x49, (byte) 0xbf, (byte) 0x3e, - (byte) 0x92, (byte) 0x57, (byte) 0x51, (byte) 0x5d, (byte) 0x36, (byte) 0x63, (byte) 0xa1, (byte) 0x31, - (byte) 0xdd - }; - - byte[] expectedHeader = new byte[]{ - (byte) 0x4c, (byte) 0x31, (byte) 0x4c, (byte) 0x01, (byte) 0x12, (byte) 0x61, (byte) 0x70, (byte) 0x69, (byte) 0x2e, (byte) 0x65, (byte) 0x78, (byte) 0x61, (byte) 0x6d, (byte) 0x70, (byte) 0x6c, (byte) 0x2e, - (byte) 0x63, (byte) 0x6f, (byte) 0x6d, (byte) 0x2f, (byte) 0x6b, (byte) 0x61, (byte) 0x73, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x56, (byte) 0x61, (byte) 0x70, (byte) 0x69, (byte) 0x2d, - (byte) 0x64, (byte) 0x65, (byte) 0x76, (byte) 0x65, (byte) 0x6c, (byte) 0x6f, (byte) 0x70, (byte) 0x30, (byte) 0x31, (byte) 0x2e, (byte) 0x64, (byte) 0x65, (byte) 0x76, (byte) 0x65, (byte) 0x6c, (byte) 0x6f, - (byte) 0x70, (byte) 0x2e, (byte) 0x76, (byte) 0x69, (byte) 0x72, (byte) 0x74, (byte) 0x72, (byte) 0x75, (byte) 0x2e, (byte) 0x63, (byte) 0x6f, (byte) 0x6d, (byte) 0x2f, (byte) 0x61, (byte) 0x63, (byte) 0x6d, - (byte) 0x2f, (byte) 0x61, (byte) 0x70, (byte) 0x69, (byte) 0x2f, (byte) 0x70, (byte) 0x6f, (byte) 0x6c, (byte) 0x69, (byte) 0x63, (byte) 0x69, (byte) 0x65, (byte) 0x73, (byte) 0x2f, (byte) 0x31, (byte) 0x61, - (byte) 0x31, (byte) 0x64, (byte) 0x35, (byte) 0x65, (byte) 0x34, (byte) 0x32, (byte) 0x2d, (byte) 0x62, (byte) 0x66, (byte) 0x39, (byte) 0x31, (byte) 0x2d, (byte) 0x34, (byte) 0x35, (byte) 0x63, (byte) 0x37, - (byte) 0x2d, (byte) 0x61, (byte) 0x38, (byte) 0x36, (byte) 0x61, (byte) 0x2d, (byte) 0x36, (byte) 0x31, (byte) 0x64, (byte) 0x35, (byte) 0x33, (byte) 0x33, (byte) 0x31, (byte) 0x63, (byte) 0x31, (byte) 0x66, - (byte) 0x35, (byte) 0x35, (byte) 0x33, (byte) 0x31, (byte) 0x63, (byte) 0x31, (byte) 0x66, (byte) 0x35, (byte) 0x35, (byte) 0x00, (byte) 0x03, (byte) 0x16, (byte) 0xd4, (byte) 0x7b, (byte) 0x2a, (byte) 0xd2, - (byte) 0xdc, (byte) 0xf1, (byte) 0x37, (byte) 0xa3, (byte) 0x9b, (byte) 0xb9, (byte) 0x7a, (byte) 0x95, (byte) 0xf8, (byte) 0x9a, (byte) 0x09, (byte) 0x37, (byte) 0x47, (byte) 0xfb, (byte) 0xf0, (byte) 0x49, - (byte) 0xbf, (byte) 0x3e, (byte) 0x92, (byte) 0x57, (byte) 0x51, (byte) 0x5d, (byte) 0x36, (byte) 0x63, (byte) 0xa1, (byte) 0x31, (byte) 0xdd - }; - -// TODO: Need to update the static data to fix this test - @Test - public void testNanoTDFHeaderRemotePolicy() throws IOException { - byte[] headerData = new byte[155]; - - // Construct empty header - encrypt use case - Header header = new Header(); - - ResourceLocator kasLocator = new ResourceLocator("https://api.exampl.com/kas"); - header.setKasLocator(kasLocator); - - ECCMode eccMode = new ECCMode((byte) 0x0); //no ecdsa binding and 'secp256r1' - header.setECCMode(eccMode); - - SymmetricAndPayloadConfig payloadConfig = new SymmetricAndPayloadConfig((byte) 0x0); // no signature and AES_256_GCM_64_TAG - header.setPayloadConfig(payloadConfig); - - PolicyInfo policyInfo = new PolicyInfo(); - policyInfo.setRemotePolicy(remotePolicyUrl); - policyInfo.setPolicyBinding(binding); - - header.setPolicyInfo(policyInfo); - header.setEphemeralKey(compressedPubKey); - - int headerSize = header.getTotalSize(); - headerSize = header.writeIntoBuffer(ByteBuffer.wrap(headerData)); - assertEquals(headerData.length, headerSize); - assertTrue(Arrays.equals(headerData, expectedHeader)); - } - -// TODO: Need to update the static data to fix this test - @Test - public void testNanoTDFReader() throws IOException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException, CertificateException, InvalidKeyException, InvalidKeySpecException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, SignatureException { - - Header header2 = new Header(); - FileInputStream fileIn = new FileInputStream("src/test/resources/javasdknanotdf.ntdf"); - DataInputStream dataIn = new DataInputStream(fileIn); - - // Read each field of the Header object from the file - byte[] magicNumberAndVersion = new byte[3]; // size of magic number and version - dataIn.readFully(magicNumberAndVersion); - header2.setMagicNumberAndVersion(magicNumberAndVersion); - - NanoTDFType.Protocol protocol = NanoTDFType.Protocol.values()[dataIn.readByte()]; - - // Read the body length - int bodyLength = dataIn.readByte(); - - // Read the body - byte[] body = new byte[bodyLength]; - dataIn.readFully(body); - - // Create a new ResourceLocator object - ResourceLocator resourceLocator = new ResourceLocator(); - resourceLocator.setProtocol(protocol); - resourceLocator.setBodyLength(bodyLength); - resourceLocator.setBody(body); - header2.setKasLocator(resourceLocator); - - ECCMode eccMode2 = new ECCMode(dataIn.readByte()); - header2.setECCMode(eccMode2); - - SymmetricAndPayloadConfig payloadConfig2 = new SymmetricAndPayloadConfig(dataIn.readByte()); - header2.setPayloadConfig(payloadConfig2); - - // Read the policy type - int remainingBytes = dataIn.available(); - - // Create a byte array to hold the remaining bytes - byte[] remainingBytesArray = new byte[remainingBytes]; - - // Read the remaining bytes into the byte array - dataIn.readFully(remainingBytesArray); - PolicyInfo policyInfo = new PolicyInfo(ByteBuffer.wrap(remainingBytesArray), header2.getECCMode()); - header2.setPolicyInfo(policyInfo); - - int sizeToRead = policyInfo.getTotalSize(); - int compressedPubKeySize = ECCMode.getECCompressedPubKeySize(header2.getECCMode().getEllipticCurveType()); - byte[] ephemeralKey = new byte[compressedPubKeySize]; // size of compressed public key - System.arraycopy(remainingBytesArray, sizeToRead, ephemeralKey, 0, ephemeralKey.length); - header2.setEphemeralKey(ephemeralKey); - - dataIn.close(); - fileIn.close(); - - assertEquals(kasUrl, header2.getKasLocator().getResourceUrl()); - } - - @Test - public void testNanoTDFEncryption() throws IOException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException, CertificateException, InvalidKeyException, InvalidKeySpecException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, SignatureException { - final int kGmacPayloadLength = 8; - final int nanoTDFIvSize = 3; - String policy = "{\"body\":{\"dataAttributes\":[],\"dissem\":[\"cn=virtru-user\",\"user@example.com\"]},\"uuid\":\"1a84b9c7-d59c-45ed-b092-c7ed7de73a07\"}"; - -// Some buffers for compare. - byte[] compressedPubKey; - byte[] headerBuffer; - byte[] encryptedPayLoad; - byte[] policyBinding; - byte[] encryptKey; + byte[] binding = new byte[] { (byte) 0x33, (byte) 0x31, (byte) 0x63, (byte) 0x31, + (byte) 0x66, (byte) 0x35, (byte) 0x35, (byte) 0x00 }; + String kasUrl = "https://api.example.com/kas"; + String remotePolicyUrl = "https://api-develop01.develop.virtru.com/acm/api/policies/1a1d5e42-bf91-45c7-a86a-61d5331c1f55"; + + // Curve - "prime256v1" + String sdkPrivateKey = "-----BEGIN PRIVATE KEY-----\n" + + "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg1HjFYV8D16BQszNW\n" + + "6Hx/JxTE53oqk5/bWaIj4qV5tOyhRANCAAQW1Hsq0tzxN6ObuXqV+JoJN0f78Em/\n" + + "PpJXUV02Y6Ex3WlxK/Oaebj8ATsbfaPaxrhyCWB3nc3w/W6+lySlLPn5\n" + + "-----END PRIVATE KEY-----"; + + String sdkPublicKey = "-----BEGIN PUBLIC KEY-----\n" + + "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFtR7KtLc8Tejm7l6lfiaCTdH+/BJ\n" + + "vz6SV1FdNmOhMd1pcSvzmnm4/AE7G32j2sa4cglgd53N8P1uvpckpSz5+Q==\n" + + "-----END PUBLIC KEY-----"; + + String kasPrivateKey = "-----BEGIN PRIVATE KEY-----\n" + + "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgu2Hmm80uUzQB1OfB\n" + + "PyMhWIyJhPA61v+j0arvcLjTwtqhRANCAASHCLUHY4szFiVV++C9+AFMkEL2gG+O\n" + + "byN4Hi7Ywl8GMPOAPcQdIeUkoTd9vub9PcuSj23I8/pLVzs23qhefoUf\n" + + "-----END PRIVATE KEY-----"; + + String kasPublicKey = "-----BEGIN PUBLIC KEY-----\n" + + "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEhwi1B2OLMxYlVfvgvfgBTJBC9oBv\n" + + "jm8jeB4u2MJfBjDzgD3EHSHlJKE3fb7m/T3Lko9tyPP6S1c7Nt6oXn6FHw==\n" + + "-----END PUBLIC KEY-----"; + + byte[] compressedPubKey = new byte[] { + (byte) 0x03, (byte) 0x16, (byte) 0xd4, (byte) 0x7b, (byte) 0x2a, (byte) 0xd2, (byte) 0xdc, + (byte) 0xf1, + (byte) 0x37, (byte) 0xa3, (byte) 0x9b, (byte) 0xb9, (byte) 0x7a, (byte) 0x95, (byte) 0xf8, + (byte) 0x9a, + (byte) 0x09, (byte) 0x37, (byte) 0x47, (byte) 0xfb, (byte) 0xf0, (byte) 0x49, (byte) 0xbf, + (byte) 0x3e, + (byte) 0x92, (byte) 0x57, (byte) 0x51, (byte) 0x5d, (byte) 0x36, (byte) 0x63, (byte) 0xa1, + (byte) 0x31, + (byte) 0xdd + }; + + byte[] expectedHeader = new byte[] { + (byte) 0x4c, (byte) 0x31, (byte) 0x4c, (byte) 0x01, (byte) 0x12, (byte) 0x61, (byte) 0x70, + (byte) 0x69, (byte) 0x2e, (byte) 0x65, (byte) 0x78, (byte) 0x61, (byte) 0x6d, (byte) 0x70, + (byte) 0x6c, (byte) 0x2e, + (byte) 0x63, (byte) 0x6f, (byte) 0x6d, (byte) 0x2f, (byte) 0x6b, (byte) 0x61, (byte) 0x73, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x56, (byte) 0x61, (byte) 0x70, + (byte) 0x69, (byte) 0x2d, + (byte) 0x64, (byte) 0x65, (byte) 0x76, (byte) 0x65, (byte) 0x6c, (byte) 0x6f, (byte) 0x70, + (byte) 0x30, (byte) 0x31, (byte) 0x2e, (byte) 0x64, (byte) 0x65, (byte) 0x76, (byte) 0x65, + (byte) 0x6c, (byte) 0x6f, + (byte) 0x70, (byte) 0x2e, (byte) 0x76, (byte) 0x69, (byte) 0x72, (byte) 0x74, (byte) 0x72, + (byte) 0x75, (byte) 0x2e, (byte) 0x63, (byte) 0x6f, (byte) 0x6d, (byte) 0x2f, (byte) 0x61, + (byte) 0x63, (byte) 0x6d, + (byte) 0x2f, (byte) 0x61, (byte) 0x70, (byte) 0x69, (byte) 0x2f, (byte) 0x70, (byte) 0x6f, + (byte) 0x6c, (byte) 0x69, (byte) 0x63, (byte) 0x69, (byte) 0x65, (byte) 0x73, (byte) 0x2f, + (byte) 0x31, (byte) 0x61, + (byte) 0x31, (byte) 0x64, (byte) 0x35, (byte) 0x65, (byte) 0x34, (byte) 0x32, (byte) 0x2d, + (byte) 0x62, (byte) 0x66, (byte) 0x39, (byte) 0x31, (byte) 0x2d, (byte) 0x34, (byte) 0x35, + (byte) 0x63, (byte) 0x37, + (byte) 0x2d, (byte) 0x61, (byte) 0x38, (byte) 0x36, (byte) 0x61, (byte) 0x2d, (byte) 0x36, + (byte) 0x31, (byte) 0x64, (byte) 0x35, (byte) 0x33, (byte) 0x33, (byte) 0x31, (byte) 0x63, + (byte) 0x31, (byte) 0x66, + (byte) 0x35, (byte) 0x35, (byte) 0x33, (byte) 0x31, (byte) 0x63, (byte) 0x31, (byte) 0x66, + (byte) 0x35, (byte) 0x35, (byte) 0x00, (byte) 0x03, (byte) 0x16, (byte) 0xd4, (byte) 0x7b, + (byte) 0x2a, (byte) 0xd2, + (byte) 0xdc, (byte) 0xf1, (byte) 0x37, (byte) 0xa3, (byte) 0x9b, (byte) 0xb9, (byte) 0x7a, + (byte) 0x95, (byte) 0xf8, (byte) 0x9a, (byte) 0x09, (byte) 0x37, (byte) 0x47, (byte) 0xfb, + (byte) 0xf0, (byte) 0x49, + (byte) 0xbf, (byte) 0x3e, (byte) 0x92, (byte) 0x57, (byte) 0x51, (byte) 0x5d, (byte) 0x36, + (byte) 0x63, (byte) 0xa1, (byte) 0x31, (byte) 0xdd + }; + + // TODO: Need to update the static data to fix this test + @Test + public void testNanoTDFHeaderRemotePolicy() throws IOException { + byte[] headerData = new byte[155]; + + // Construct empty header - encrypt use case + Header header = new Header(); + + ResourceLocator kasLocator = new ResourceLocator("https://api.exampl.com/kas"); + header.setKasLocator(kasLocator); + + ECCMode eccMode = new ECCMode((byte) 0x0); // no ecdsa binding and 'secp256r1' + header.setECCMode(eccMode); + + SymmetricAndPayloadConfig payloadConfig = new SymmetricAndPayloadConfig((byte) 0x0); // no signature and + // AES_256_GCM_64_TAG + header.setPayloadConfig(payloadConfig); + + PolicyInfo policyInfo = new PolicyInfo(); + policyInfo.setRemotePolicy(remotePolicyUrl); + policyInfo.setPolicyBinding(binding); + + header.setPolicyInfo(policyInfo); + header.setEphemeralKey(compressedPubKey); + + int headerSize = header.getTotalSize(); + headerSize = header.writeIntoBuffer(ByteBuffer.wrap(headerData)); + assertEquals(headerData.length, headerSize); + assertTrue(Arrays.equals(headerData, expectedHeader)); + } - SymmetricAndPayloadConfig payloadConfig = new SymmetricAndPayloadConfig((byte) 0x0); - int tagSize = payloadConfig.sizeOfAuthTagForCipher(payloadConfig.getCipherType()); - byte[] tag = new byte[tagSize]; + // TODO: Need to update the static data to fix this test + @Test + public void testNanoTDFReader() + throws IOException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, + NoSuchProviderException, CertificateException, InvalidKeyException, InvalidKeySpecException, + NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, SignatureException { - ECCMode eccMode = new ECCMode((byte) 0x0); //no ecdsa binding and 'secp256r1' - ECKeyPair sdkECKeyPair = new ECKeyPair(eccMode.getCurveName(), ECKeyPair.ECAlgorithm.ECDH); - String sdkPrivateKeyForEncrypt = sdkECKeyPair.privateKeyInPEMFormat(); - String sdkPublicKeyForEncrypt = sdkECKeyPair.publicKeyInPEMFormat(); + Header header2 = new Header(); + FileInputStream fileIn = new FileInputStream("src/test/resources/javasdknanotdf.ntdf"); + DataInputStream dataIn = new DataInputStream(fileIn); - ECKeyPair kasECKeyPair = new ECKeyPair(eccMode.getCurveName(), ECKeyPair.ECAlgorithm.ECDH); - String kasPublicKey = kasECKeyPair.publicKeyInPEMFormat(); - // Encrypt - Header header = new Header(); - - ResourceLocator kasLocator = new ResourceLocator("https://test.com"); - header.setKasLocator(kasLocator); - - header.setECCMode(eccMode); - header.setPayloadConfig(payloadConfig); + // Read each field of the Header object from the file + byte[] magicNumberAndVersion = new byte[3]; // size of magic number and version + dataIn.readFully(magicNumberAndVersion); + header2.setMagicNumberAndVersion(magicNumberAndVersion); - byte[] secret = ECKeyPair.computeECDHKey(ECKeyPair.publicKeyFromPem(kasPublicKey), ECKeyPair.privateKeyFromPem(sdkPrivateKeyForEncrypt)); - byte[] saltValue = {'V', 'I', 'R', 'T', 'R', 'U'}; - encryptKey = ECKeyPair.calculateHKDF(saltValue, secret); - - // Encrypt the policy with key from KDF - int encryptedPayLoadSize = policy.length() + nanoTDFIvSize + tagSize; - encryptedPayLoad = new byte[encryptedPayLoadSize]; + NanoTDFType.Protocol protocol = NanoTDFType.Protocol.values()[dataIn.readByte()]; - SecureRandom secureRandom = new SecureRandom(); - byte[] iv = new byte[nanoTDFIvSize]; - secureRandom.nextBytes(iv); + // Read the body length + int bodyLength = dataIn.readByte(); - // Adjust the span to add the IV vector at the start of the buffer - byte[] encryptBufferSpan = Arrays.copyOfRange(encryptedPayLoad, nanoTDFIvSize, encryptedPayLoad.length); + // Read the body + byte[] body = new byte[bodyLength]; + dataIn.readFully(body); - AesGcm encoder = new AesGcm(encryptKey); - encoder.encrypt(encryptBufferSpan); - - byte[] authTag = new byte[tag.length]; - //encoder.finish(authTag); + // Create a new ResourceLocator object + ResourceLocator resourceLocator = new ResourceLocator(); + resourceLocator.setProtocol(protocol); + resourceLocator.setBodyLength(bodyLength); + resourceLocator.setBody(body); + header2.setKasLocator(resourceLocator); - // Copy IV at start - System.arraycopy(iv, 0, encryptedPayLoad, 0, iv.length); + ECCMode eccMode2 = new ECCMode(dataIn.readByte()); + header2.setECCMode(eccMode2); - // Copy tag at end - System.arraycopy(tag, 0, encryptedPayLoad, nanoTDFIvSize + policy.length(), tag.length); + SymmetricAndPayloadConfig payloadConfig2 = new SymmetricAndPayloadConfig(dataIn.readByte()); + header2.setPayloadConfig(payloadConfig2); + // Read the policy type + int remainingBytes = dataIn.available(); + + // Create a byte array to hold the remaining bytes + byte[] remainingBytesArray = new byte[remainingBytes]; + + // Read the remaining bytes into the byte array + dataIn.readFully(remainingBytesArray); + PolicyInfo policyInfo = new PolicyInfo(ByteBuffer.wrap(remainingBytesArray), header2.getECCMode()); + header2.setPolicyInfo(policyInfo); - // Create an encrypted policy. - PolicyInfo encryptedPolicy = new PolicyInfo(); - encryptedPolicy.setEmbeddedEncryptedTextPolicy(encryptedPayLoad); - - byte[] digest = encryptedPayLoad; - if (eccMode.isECDSABindingEnabled()) { - // Calculate the ecdsa binding. - policyBinding = ECKeyPair.computeECDSASig(digest, ECKeyPair.privateKeyFromPem(sdkPrivateKeyForEncrypt)); - encryptedPolicy.setPolicyBinding(policyBinding); - } else { - // Calculate the gmac binding - byte[] gmac = Arrays.copyOfRange(digest, digest.length - kGmacPayloadLength, digest.length); - encryptedPolicy.setPolicyBinding(gmac); - } - - header.setPolicyInfo(encryptedPolicy); + int sizeToRead = policyInfo.getTotalSize(); + int compressedPubKeySize = ECCMode + .getECCompressedPubKeySize(header2.getECCMode().getEllipticCurveType()); + byte[] ephemeralKey = new byte[compressedPubKeySize]; // size of compressed public key + System.arraycopy(remainingBytesArray, sizeToRead, ephemeralKey, 0, ephemeralKey.length); + header2.setEphemeralKey(ephemeralKey); - compressedPubKey = ECKeyPair.compressECPublickey(sdkPublicKeyForEncrypt); - header.setEphemeralKey(compressedPubKey); + dataIn.close(); + fileIn.close(); - int headerSize = header.getTotalSize(); - headerBuffer = new byte[headerSize]; - int sizeWritten = header.writeIntoBuffer(ByteBuffer.wrap(headerBuffer)); - assertEquals(sizeWritten, headerSize); - } + assertEquals(kasUrl, header2.getKasLocator().getResourceUrl()); + } + + @Test + public void testNanoTDFEncryption() + throws IOException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, + NoSuchProviderException, CertificateException, InvalidKeyException, InvalidKeySpecException, + NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, SignatureException { + final int kGmacPayloadLength = 8; + final int nanoTDFIvSize = 3; + String policy = "{\"body\":{\"dataAttributes\":[],\"dissem\":[\"cn=virtru-user\",\"user@example.com\"]},\"uuid\":\"1a84b9c7-d59c-45ed-b092-c7ed7de73a07\"}"; + + // Some buffers for compare. + byte[] compressedPubKey; + byte[] headerBuffer; + byte[] encryptedPayLoad; + byte[] policyBinding; + byte[] encryptKey; + + SymmetricAndPayloadConfig payloadConfig = new SymmetricAndPayloadConfig((byte) 0x0); + int tagSize = SymmetricAndPayloadConfig.sizeOfAuthTagForCipher(payloadConfig.getCipherType()); + byte[] tag = new byte[tagSize]; + + ECCMode eccMode = new ECCMode((byte) 0x0); // no ecdsa binding and 'secp256r1' + ECKeyPair sdkECKeyPair = new ECKeyPair(eccMode.getCurveName(), ECKeyPair.ECAlgorithm.ECDH); + String sdkPrivateKeyForEncrypt = sdkECKeyPair.privateKeyInPEMFormat(); + String sdkPublicKeyForEncrypt = sdkECKeyPair.publicKeyInPEMFormat(); + + ECKeyPair kasECKeyPair = new ECKeyPair(eccMode.getCurveName(), ECKeyPair.ECAlgorithm.ECDH); + String kasPublicKey = kasECKeyPair.publicKeyInPEMFormat(); + // Encrypt + Header header = new Header(); + + ResourceLocator kasLocator = new ResourceLocator("https://test.com"); + header.setKasLocator(kasLocator); + + header.setECCMode(eccMode); + header.setPayloadConfig(payloadConfig); + + byte[] secret = ECKeyPair.computeECDHKey(ECKeyPair.publicKeyFromPem(kasPublicKey), + ECKeyPair.privateKeyFromPem(sdkPrivateKeyForEncrypt)); + byte[] saltValue = { 'V', 'I', 'R', 'T', 'R', 'U' }; + encryptKey = ECKeyPair.calculateHKDF(saltValue, secret); + + // Encrypt the policy with key from KDF + int encryptedPayLoadSize = policy.length() + nanoTDFIvSize + tagSize; + encryptedPayLoad = new byte[encryptedPayLoadSize]; + + SecureRandom secureRandom = new SecureRandom(); + byte[] iv = new byte[nanoTDFIvSize]; + secureRandom.nextBytes(iv); + + // Adjust the span to add the IV vector at the start of the buffer + byte[] encryptBufferSpan = Arrays.copyOfRange(encryptedPayLoad, nanoTDFIvSize, encryptedPayLoad.length); + + AesGcm encoder = new AesGcm(encryptKey); + encoder.encrypt(encryptBufferSpan); + + byte[] authTag = new byte[tag.length]; + // encoder.finish(authTag); + + // Copy IV at start + System.arraycopy(iv, 0, encryptedPayLoad, 0, iv.length); + + // Copy tag at end + System.arraycopy(tag, 0, encryptedPayLoad, nanoTDFIvSize + policy.length(), tag.length); + + // Create an encrypted policy. + PolicyInfo encryptedPolicy = new PolicyInfo(); + encryptedPolicy.setEmbeddedEncryptedTextPolicy(encryptedPayLoad); + + byte[] digest = encryptedPayLoad; + if (eccMode.isECDSABindingEnabled()) { + // Calculate the ecdsa binding. + policyBinding = ECKeyPair.computeECDSASig(digest, + ECKeyPair.privateKeyFromPem(sdkPrivateKeyForEncrypt)); + encryptedPolicy.setPolicyBinding(policyBinding); + } else { + // Calculate the gmac binding + byte[] gmac = Arrays.copyOfRange(digest, digest.length - kGmacPayloadLength, digest.length); + encryptedPolicy.setPolicyBinding(gmac); + } + + header.setPolicyInfo(encryptedPolicy); + + compressedPubKey = ECKeyPair.compressECPublickey(sdkPublicKeyForEncrypt); + header.setEphemeralKey(compressedPubKey); + + int headerSize = header.getTotalSize(); + headerBuffer = new byte[headerSize]; + int sizeWritten = header.writeIntoBuffer(ByteBuffer.wrap(headerBuffer)); + assertEquals(sizeWritten, headerSize); + } }