Skip to content

Commit e076d11

Browse files
mkleenesujankotadmihalcik-virtru
authored
fix(sdk): remove hex encoding (#213)
* add a `tdfVersion` attribute to the manifest * when loading older versions hex-encode hashes * Adds `--version` option to command line tool --------- Co-authored-by: sujan kota <[email protected]> Co-authored-by: David Mihalcik <[email protected]>
1 parent e0f8caf commit e076d11

File tree

6 files changed

+89
-54
lines changed

6 files changed

+89
-54
lines changed

cmdline/src/main/java/io/opentdf/platform/Command.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,16 @@
5252

5353
import javax.net.ssl.TrustManager;
5454

55-
56-
@CommandLine.Command(name = "tdf", subcommands = {HelpCommand.class})
55+
@CommandLine.Command(
56+
name = "tdf",
57+
subcommands = {HelpCommand.class},
58+
version = "{\"version\":\"0.7.5\",\"tdfSpecVersion\":\"4.3.0\"}"
59+
)
5760
class Command {
5861

62+
@Option(names = {"-V", "--version"}, versionHelp = true, description = "display version info")
63+
boolean versionInfoRequested;
64+
5965
private static final String PRIVATE_KEY_HEADER = "-----BEGIN PRIVATE KEY-----";
6066
private static final String PRIVATE_KEY_FOOTER = "-----END PRIVATE KEY-----";
6167
private static final String PEM_HEADER = "-----BEGIN (.*)-----";
@@ -150,7 +156,7 @@ void encrypt(
150156
@Option(names = { "--mime-type" }, defaultValue = Option.NULL_VALUE) Optional<String> mimeType,
151157
@Option(names = { "--with-assertions" }, defaultValue = Option.NULL_VALUE) Optional<String> assertion)
152158

153-
throws IOException, JOSEException, AutoConfigureException, InterruptedException, ExecutionException {
159+
throws IOException, JOSEException, AutoConfigureException, InterruptedException, ExecutionException, DecoderException {
154160

155161
var sdk = buildSDK();
156162
var kasInfos = kas.stream().map(k -> {

examples/src/main/java/io/opentdf/platform/EncryptExample.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@
66
import java.io.FileOutputStream;
77

88
import com.nimbusds.jose.JOSEException;
9+
import org.apache.commons.codec.DecoderException;
10+
911
import java.io.IOException;
1012
import java.util.concurrent.ExecutionException;
1113

1214
public class EncryptExample {
13-
public static void main(String[] args) throws IOException, JOSEException, AutoConfigureException, InterruptedException, ExecutionException {
15+
public static void main(String[] args) throws IOException, JOSEException, AutoConfigureException, InterruptedException, ExecutionException, DecoderException {
1416
String clientId = "opentdf";
1517
String clientSecret = "secret";
1618
String platformEndpoint = "localhost:8080";

sdk/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@
4444
<artifactId>gson</artifactId>
4545
<version>2.11.0</version>
4646
</dependency>
47+
<dependency>
48+
<groupId>com.vdurmont</groupId>
49+
<artifactId>semver4j</artifactId>
50+
<version>3.1.0</version>
51+
</dependency>
4752
<dependency>
4853
<groupId>commons-codec</groupId>
4954
<artifactId>commons-codec</artifactId>

sdk/src/main/java/io/opentdf/platform/sdk/Manifest.java

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,28 @@
11
package io.opentdf.platform.sdk;
22

3-
import com.google.gson.*;
3+
import com.google.gson.Gson;
4+
import com.google.gson.GsonBuilder;
5+
import com.google.gson.JsonDeserializationContext;
6+
import com.google.gson.JsonDeserializer;
7+
import com.google.gson.JsonElement;
8+
import com.google.gson.JsonParseException;
9+
import com.google.gson.JsonSerializationContext;
10+
import com.google.gson.JsonSerializer;
411
import com.google.gson.annotations.JsonAdapter;
512
import com.google.gson.annotations.SerializedName;
6-
import com.nimbusds.jose.*;
13+
import com.nimbusds.jose.JOSEException;
14+
import com.nimbusds.jose.JWSAlgorithm;
15+
import com.nimbusds.jose.JWSHeader;
16+
import com.nimbusds.jose.JWSSigner;
17+
import com.nimbusds.jose.JWSVerifier;
18+
import com.nimbusds.jose.KeyLengthException;
719
import com.nimbusds.jose.crypto.MACSigner;
820
import com.nimbusds.jose.crypto.MACVerifier;
921
import com.nimbusds.jose.crypto.RSASSASigner;
1022
import com.nimbusds.jose.crypto.RSASSAVerifier;
1123
import com.nimbusds.jwt.JWTClaimsSet;
1224
import com.nimbusds.jwt.SignedJWT;
13-
1425
import io.opentdf.platform.sdk.TDF.AssertionException;
15-
1626
import org.apache.commons.codec.binary.Hex;
1727
import org.erdtman.jcs.JsonCanonicalizer;
1828

@@ -44,21 +54,20 @@ public class Manifest {
4454
private static final Gson gson = new GsonBuilder()
4555
.registerTypeAdapter(Manifest.class, new ManifestDeserializer())
4656
.create();
57+
@SerializedName(value = "schemaVersion")
58+
String tdfVersion;
4759

4860
@Override
4961
public boolean equals(Object o) {
50-
if (this == o)
51-
return true;
52-
if (o == null || getClass() != o.getClass())
53-
return false;
62+
if (this == o) return true;
63+
if (o == null || getClass() != o.getClass()) return false;
5464
Manifest manifest = (Manifest) o;
55-
return Objects.equals(encryptionInformation, manifest.encryptionInformation)
56-
&& Objects.equals(payload, manifest.payload) && Objects.equals(assertions, manifest.assertions);
65+
return Objects.equals(tdfVersion, manifest.tdfVersion) && Objects.equals(encryptionInformation, manifest.encryptionInformation) && Objects.equals(payload, manifest.payload) && Objects.equals(assertions, manifest.assertions);
5766
}
5867

5968
@Override
6069
public int hashCode() {
61-
return Objects.hash(encryptionInformation, payload, assertions);
70+
return Objects.hash(tdfVersion, encryptionInformation, payload, assertions);
6271
}
6372

6473
private static class PolicyBindingSerializer implements JsonDeserializer<Object>, JsonSerializer<Object> {
@@ -183,6 +192,7 @@ static public class KeyAccess {
183192
public String encryptedMetadata;
184193
public String kid;
185194
public String sid;
195+
public String schemaVersion;
186196

187197
@Override
188198
public boolean equals(Object o) {
@@ -195,12 +205,13 @@ public boolean equals(Object o) {
195205
&& Objects.equals(protocol, keyAccess.protocol) && Objects.equals(wrappedKey, keyAccess.wrappedKey)
196206
&& Objects.equals(policyBinding, keyAccess.policyBinding)
197207
&& Objects.equals(encryptedMetadata, keyAccess.encryptedMetadata)
198-
&& Objects.equals(kid, keyAccess.kid);
208+
&& Objects.equals(kid, keyAccess.kid)
209+
&& Objects.equals(schemaVersion, keyAccess.schemaVersion);
199210
}
200211

201212
@Override
202213
public int hashCode() {
203-
return Objects.hash(keyType, url, protocol, wrappedKey, policyBinding, encryptedMetadata, kid);
214+
return Objects.hash(keyType, url, protocol, wrappedKey, policyBinding, encryptedMetadata, kid, schemaVersion);
204215
}
205216
}
206217

sdk/src/main/java/io/opentdf/platform/sdk/TDF.java

Lines changed: 47 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,9 @@
1717
import org.slf4j.Logger;
1818
import org.slf4j.LoggerFactory;
1919

20-
import java.io.BufferedReader;
2120
import java.io.ByteArrayOutputStream;
2221
import java.io.IOException;
2322
import java.io.InputStream;
24-
import java.io.InputStreamReader;
2523
import java.io.OutputStream;
2624
import java.io.StringReader;
2725
import java.nio.channels.SeekableByteChannel;
@@ -39,6 +37,8 @@
3937
*/
4038
public class TDF {
4139

40+
private static final String TDF_VERSION = "4.3.0";
41+
private static final String KEY_ACCESS_SECHMA_VERSION = "4.3.0";
4242
private final long maximumSize;
4343

4444
/**
@@ -203,6 +203,7 @@ PolicyObject createPolicyObject(List<Autoconfigure.AttributeValueFQN> attributes
203203
private static final Base64.Encoder encoder = Base64.getEncoder();
204204

205205
private void prepareManifest(Config.TDFConfig tdfConfig, SDK.KAS kas) {
206+
manifest.tdfVersion = TDF_VERSION;
206207
manifest.encryptionInformation.keyAccessType = kSplitKeyType;
207208
manifest.encryptionInformation.keyAccessObj = new ArrayList<>();
208209

@@ -307,6 +308,7 @@ private void prepareManifest(Config.TDFConfig tdfConfig, SDK.KAS kas) {
307308
keyAccess.wrappedKey = encoder.encodeToString(wrappedKey);
308309
keyAccess.encryptedMetadata = encryptedMetadata;
309310
keyAccess.sid = splitID;
311+
keyAccess.schemaVersion = KEY_ACCESS_SECHMA_VERSION;
310312

311313
manifest.encryptionInformation.keyAccessObj.add(keyAccess);
312314
}
@@ -370,6 +372,8 @@ public void readPayload(OutputStream outputStream) throws TDFReadFailed,
370372
throw new TDFReadFailed("failed to read payload");
371373
}
372374

375+
var isLegacyTdf = manifest.tdfVersion == null || manifest.tdfVersion.isEmpty();
376+
373377
if (manifest.payload.isEncrypted) {
374378
String segHashAlg = manifest.encryptionInformation.integrityInformation.segmentHashAlg;
375379
Config.IntegrityAlgorithm sigAlg = Config.IntegrityAlgorithm.HS256;
@@ -378,9 +382,11 @@ public void readPayload(OutputStream outputStream) throws TDFReadFailed,
378382
}
379383

380384
var payloadSig = calculateSignature(readBuf, payloadKey, sigAlg);
381-
byte[] payloadSigAsBytes = payloadSig.getBytes(StandardCharsets.UTF_8);
385+
if (isLegacyTdf) {
386+
payloadSig = Hex.encodeHexString(payloadSig).getBytes(StandardCharsets.UTF_8);
387+
}
382388

383-
if (segment.hash.compareTo(Base64.getEncoder().encodeToString(payloadSigAsBytes)) != 0) {
389+
if (segment.hash.compareTo(Base64.getEncoder().encodeToString(payloadSig)) != 0) {
384390
throw new SegmentSignatureMismatch("segment signature miss match");
385391
}
386392

@@ -403,24 +409,22 @@ public PolicyObject readPolicyObject() {
403409
}
404410
}
405411

406-
private static String calculateSignature(byte[] data, byte[] secret, Config.IntegrityAlgorithm algorithm) {
412+
private static byte[] calculateSignature(byte[] data, byte[] secret, Config.IntegrityAlgorithm algorithm) {
407413
if (algorithm == Config.IntegrityAlgorithm.HS256) {
408-
byte[] hmac = CryptoUtils.CalculateSHA256Hmac(secret, data);
409-
return Hex.encodeHexString(hmac);
414+
return CryptoUtils.CalculateSHA256Hmac(secret, data);
410415
}
411416

412417
if (kGMACPayloadLength > data.length) {
413418
throw new FailedToCreateGMAC("fail to create gmac signature");
414419
}
415420

416-
byte[] gmacPayload = Arrays.copyOfRange(data, data.length - kGMACPayloadLength, data.length);
417-
return Hex.encodeHexString(gmacPayload);
421+
return Arrays.copyOfRange(data, data.length - kGMACPayloadLength, data.length);
418422
}
419423

420424
public TDFObject createTDF(InputStream payload,
421425
OutputStream outputStream,
422426
Config.TDFConfig tdfConfig, SDK.KAS kas, AttributesServiceFutureStub attrService)
423-
throws IOException, JOSEException, AutoConfigureException, InterruptedException, ExecutionException {
427+
throws IOException, JOSEException, AutoConfigureException, InterruptedException, ExecutionException, DecoderException {
424428

425429
if (tdfConfig.autoconfigure) {
426430
Autoconfigure.Granter granter = new Autoconfigure.Granter(new ArrayList<>());
@@ -454,7 +458,7 @@ public TDFObject createTDF(InputStream payload,
454458
long encryptedSegmentSize = tdfConfig.defaultSegmentSize + kGcmIvSize + kAesBlockSize;
455459
TDFWriter tdfWriter = new TDFWriter(outputStream);
456460

457-
StringBuilder aggregateHash = new StringBuilder();
461+
ByteArrayOutputStream aggregateHash = new ByteArrayOutputStream();
458462
byte[] readBuf = new byte[tdfConfig.defaultSegmentSize];
459463

460464
tdfObject.manifest.encryptionInformation.integrityInformation.segments = new ArrayList<>();
@@ -476,17 +480,17 @@ public TDFObject createTDF(InputStream payload,
476480
}
477481

478482
byte[] cipherData;
479-
String segmentSig;
483+
byte[] segmentSig;
480484
Manifest.Segment segmentInfo = new Manifest.Segment();
481485

482486
// encrypt
483487
cipherData = tdfObject.aesGcm.encrypt(readBuf, 0, readThisLoop).asBytes();
484488
payloadOutput.write(cipherData);
485489

486490
segmentSig = calculateSignature(cipherData, tdfObject.payloadKey, tdfConfig.segmentIntegrityAlgorithm);
487-
segmentInfo.hash = Base64.getEncoder().encodeToString(segmentSig.getBytes(StandardCharsets.UTF_8));
491+
segmentInfo.hash = Base64.getEncoder().encodeToString(segmentSig);
488492

489-
aggregateHash.append(segmentSig);
493+
aggregateHash.write(segmentSig);
490494
segmentInfo.segmentSize = readThisLoop;
491495
segmentInfo.encryptedSegmentSize = cipherData.length;
492496

@@ -496,9 +500,9 @@ public TDFObject createTDF(InputStream payload,
496500

497501
Manifest.RootSignature rootSignature = new Manifest.RootSignature();
498502

499-
String rootSig = calculateSignature(aggregateHash.toString().getBytes(),
503+
byte[] rootSig = calculateSignature(aggregateHash.toByteArray(),
500504
tdfObject.payloadKey, tdfConfig.integrityAlgorithm);
501-
rootSignature.signature = Base64.getEncoder().encodeToString(rootSig.getBytes(StandardCharsets.UTF_8));
505+
rootSignature.signature = Base64.getEncoder().encodeToString(rootSig);
502506

503507
String alg = kGmacIntegrityAlgorithm;
504508
if (tdfConfig.integrityAlgorithm == Config.IntegrityAlgorithm.HS256) {
@@ -534,19 +538,24 @@ public TDFObject createTDF(InputStream payload,
534538
assertion.statement = assertionConfig.statement;
535539
assertion.appliesToState = assertionConfig.appliesToState.toString();
536540

537-
var assertionHash = assertion.hash();
538-
var completeHashBuilder = new StringBuilder(aggregateHash);
539-
completeHashBuilder.append(assertionHash);
541+
var assertionHashAsHex = assertion.hash();
542+
var assertionHash = Hex.decodeHex(assertionHashAsHex);
543+
byte[] completeHash = new byte[aggregateHash.size() + assertionHash.length];
544+
System.arraycopy(aggregateHash.toByteArray(), 0, completeHash, 0, aggregateHash.size());
545+
System.arraycopy(assertionHash, 0, completeHash, aggregateHash.size(), assertionHash.length);
540546

541-
var encodedHash = Base64.getEncoder().encodeToString(completeHashBuilder.toString().getBytes());
547+
var encodedHash = Base64.getEncoder().encodeToString(completeHash);
542548

543549
var assertionSigningKey = new AssertionConfig.AssertionKey(AssertionConfig.AssertionKeyAlg.HS256,
544550
tdfObject.aesGcm.getKey());
545551
if (assertionConfig.signingKey != null && assertionConfig.signingKey.isDefined()) {
546552
assertionSigningKey = assertionConfig.signingKey;
547553
}
548-
549-
assertion.sign(new Manifest.Assertion.HashValues(assertionHash, encodedHash), assertionSigningKey);
554+
var hashValues = new Manifest.Assertion.HashValues(
555+
assertionHashAsHex,
556+
encodedHash
557+
);
558+
assertion.sign(hashValues, assertionSigningKey);
550559
signedAssertions.add(assertion);
551560
}
552561

@@ -594,7 +603,7 @@ public Reader loadTDF(SeekableByteChannel tdf, SDK.KAS kas,
594603
byte[] payloadKey = new byte[GCM_KEY_SIZE];
595604
String unencryptedMetadata = null;
596605

597-
Set<String> knownSplits = new HashSet<String>();
606+
Set<String> knownSplits = new HashSet<>();
598607
Set<String> foundSplits = new HashSet<>();
599608

600609
Map<Autoconfigure.KeySplitStep, Exception> skippedSplits = new HashMap<>();
@@ -632,7 +641,7 @@ public Reader loadTDF(SeekableByteChannel tdf, SDK.KAS kas,
632641
AesGcm aesGcm = new AesGcm(unwrappedKey);
633642

634643
String decodedMetadata = new String(Base64.getDecoder().decode(keyAccess.encryptedMetadata),
635-
"UTF-8");
644+
StandardCharsets.UTF_8);
636645
EncryptedMetadata encryptedMetadata = gson.fromJson(decodedMetadata, EncryptedMetadata.class);
637646

638647
var encryptedData = new AesGcm.Encrypted(
@@ -679,15 +688,18 @@ public Reader loadTDF(SeekableByteChannel tdf, SDK.KAS kas,
679688
}
680689

681690
String rootSigValue;
691+
boolean isLegacyTdf = manifest.tdfVersion == null || manifest.tdfVersion.isEmpty();
682692
if (manifest.payload.isEncrypted) {
683693
Config.IntegrityAlgorithm sigAlg = Config.IntegrityAlgorithm.HS256;
684694
if (rootAlgorithm.compareToIgnoreCase(kGmacIntegrityAlgorithm) == 0) {
685695
sigAlg = Config.IntegrityAlgorithm.GMAC;
686696
}
687697

688698
var sig = calculateSignature(aggregateHash.toByteArray(), payloadKey, sigAlg);
689-
rootSigValue = Base64.getEncoder().encodeToString(sig.getBytes(StandardCharsets.UTF_8));
690-
699+
if (isLegacyTdf) {
700+
sig = Hex.encodeHexString(sig).getBytes();
701+
}
702+
rootSigValue = Base64.getEncoder().encodeToString(sig);
691703
} else {
692704
rootSigValue = Base64.getEncoder().encodeToString(digest.digest(aggregateHash.toString().getBytes()));
693705
}
@@ -703,6 +715,7 @@ public Reader loadTDF(SeekableByteChannel tdf, SDK.KAS kas,
703715
throw new SegmentSizeMismatch("mismatch encrypted segment size in manifest");
704716
}
705717

718+
var aggregateHashByteArrayBytes = aggregateHash.toByteArray();
706719
// Validate assertions
707720
for (var assertion : manifest.assertions) {
708721
// Skip assertion verification if disabled
@@ -721,16 +734,18 @@ public Reader loadTDF(SeekableByteChannel tdf, SDK.KAS kas,
721734
}
722735

723736
var hashValues = assertion.verify(assertionKey);
724-
var assertionAsJson = gson.toJson(assertion);
725-
JsonCanonicalizer jc = new JsonCanonicalizer(assertionAsJson);
726-
var hashOfAssertion = Hex.encodeHexString(digest.digest(jc.getEncodedUTF8()));
727-
var signature = aggregateHash + hashOfAssertion;
728-
var encodeSignature = Base64.getEncoder().encodeToString(signature.getBytes());
737+
var hashOfAssertionAsHex = assertion.hash();
729738

730-
if (!Objects.equals(hashOfAssertion, hashValues.getAssertionHash())) {
739+
if (!Objects.equals(hashOfAssertionAsHex, hashValues.getAssertionHash())) {
731740
throw new AssertionException("assertion hash mismatch", assertion.id);
732741
}
733742

743+
byte[] hashOfAssertion = isLegacyTdf ? hashOfAssertionAsHex.getBytes(StandardCharsets.UTF_8) : Hex.decodeHex(hashOfAssertionAsHex);
744+
var signature = new byte[aggregateHashByteArrayBytes.length + hashOfAssertion.length];
745+
System.arraycopy(aggregateHashByteArrayBytes, 0, signature, 0, aggregateHashByteArrayBytes.length);
746+
System.arraycopy(hashOfAssertion, 0, signature, aggregateHashByteArrayBytes.length, hashOfAssertion.length);
747+
var encodeSignature = Base64.getEncoder().encodeToString(signature);
748+
734749
if (!Objects.equals(encodeSignature, hashValues.getSignature())) {
735750
throw new AssertionException("failed integrity check on assertion signature", assertion.id);
736751
}

0 commit comments

Comments
 (0)