Skip to content

Commit e684b3d

Browse files
authored
feat(kms): add integrity verification to samples of crypto operations (GoogleCloudPlatform#4286)
* feat(kms): add integrity verification to samples of crypto operations * Populate plaintextCrc32c in EncryptSymmetric.java * Fix guava dependency * Fix build errors * Fix some more build errors * Fix Int64Value construction * Add protobuf-java dependency * fix typo * Test implicit Int64Value construction * Add EncryptResponse integrity verification * Deal with long to int implicit conversion * One more lossy conversion * Test something * Fix IllegalStateException caused by fetching Long from HashCode * Cleanup EncryptSymmetric.java * Fix build error * Add print outs to debug ciphertext checksum mismatch * Some more debug prints * ugghhh I should really quit already * Remove my hardcoded test project * Remove test plaintext override * Comment out debug print outs * Clean up debug print outs * Add integrity verification to DecryptSymmetric.java * Fix imports in DecryptSymmetric.java * Add integrity verification to AsymmetricDecrypt * Fix build error by adding explicit conversion to string * Add integrity verification to GetPublicKey.java * Fix build error * Add integrity verification to SignAsymmetric.java * Remove response handling from try block to see impact on failing tests * Fix build error * Add -e to mvn command and experiment with prints to STDERR * Add -Dsurefire.useFile=false to run_tests.sh * Add -D.failsafe.useFile=false flag to run_tests.sh * Add some debug prints * Fix build error * Adjust string to bytes[] conversion * Try HashCode.padToLong in EncryptSymmetric and DecryptSymmetric * Clean up debug traces * Fix compilation issue * Clean up some more * Fix typo
1 parent 2f57f39 commit e684b3d

File tree

6 files changed

+194
-12
lines changed

6 files changed

+194
-12
lines changed

kms/pom.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,18 @@
3434
<version>3.14.0</version>
3535
</dependency>
3636

37+
<dependency>
38+
<groupId>com.google.protobuf</groupId>
39+
<artifactId>protobuf-java</artifactId>
40+
<version>3.14.0</version>
41+
</dependency>
42+
43+
<dependency>
44+
<groupId>com.google.guava</groupId>
45+
<artifactId>guava</artifactId>
46+
<version>29.0-android</version>
47+
</dependency>
48+
3749
<!-- test dependencies -->
3850
<dependency>
3951
<groupId>junit</groupId>

kms/src/main/java/kms/DecryptAsymmetric.java

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,15 @@
1717
package kms;
1818

1919
// [START kms_decrypt_asymmetric]
20+
import com.google.cloud.kms.v1.AsymmetricDecryptRequest;
2021
import com.google.cloud.kms.v1.AsymmetricDecryptResponse;
2122
import com.google.cloud.kms.v1.CryptoKeyVersionName;
2223
import com.google.cloud.kms.v1.KeyManagementServiceClient;
24+
import com.google.common.hash.HashCode;
25+
import com.google.common.hash.HashFunction;
26+
import com.google.common.hash.Hashing;
2327
import com.google.protobuf.ByteString;
28+
import com.google.protobuf.Int64Value;
2429
import java.io.IOException;
2530

2631
public class DecryptAsymmetric {
@@ -56,11 +61,41 @@ public void decryptAsymmetric(
5661
CryptoKeyVersionName keyVersionName =
5762
CryptoKeyVersionName.of(projectId, locationId, keyRingId, keyId, keyVersionId);
5863

64+
// Optional, but recommended: compute ciphertext's CRC32C. See helpers below.
65+
long ciphertextCrc32c = getCrc32cAsLong(ciphertext);
66+
5967
// Decrypt the ciphertext.
60-
AsymmetricDecryptResponse response =
61-
client.asymmetricDecrypt(keyVersionName, ByteString.copyFrom(ciphertext));
68+
AsymmetricDecryptRequest request =
69+
AsymmetricDecryptRequest.newBuilder()
70+
.setName(keyVersionName.toString())
71+
.setCiphertext(ByteString.copyFrom(ciphertext))
72+
.setCiphertextCrc32C(
73+
Int64Value.newBuilder().setValue(ciphertextCrc32c).build())
74+
.build();
75+
AsymmetricDecryptResponse response = client.asymmetricDecrypt(request);
76+
77+
// Optional, but recommended: perform integrity verification on response.
78+
// For more details on ensuring E2E in-transit integrity to and from Cloud KMS visit:
79+
// https://cloud.google.com/kms/docs/data-integrity-guidelines
80+
if (!response.getVerifiedCiphertextCrc32C()) {
81+
throw new IOException("AsymmetricDecrypt: request to server corrupted");
82+
}
83+
84+
if (!crcMatches(response.getPlaintextCrc32C().getValue(),
85+
response.getPlaintext().toByteArray())) {
86+
throw new IOException("AsymmetricDecrypt: response from server corrupted");
87+
}
88+
6289
System.out.printf("Plaintext: %s%n", response.getPlaintext().toStringUtf8());
6390
}
6491
}
92+
93+
private long getCrc32cAsLong(byte[] data) {
94+
return Hashing.crc32c().hashBytes(data).padToLong();
95+
}
96+
97+
private boolean crcMatches(long expectedCrc, byte[] data) {
98+
return expectedCrc == getCrc32cAsLong(data);
99+
}
65100
}
66101
// [END kms_decrypt_asymmetric]

kms/src/main/java/kms/DecryptSymmetric.java

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,18 @@
1818

1919
// [START kms_decrypt_symmetric]
2020
import com.google.cloud.kms.v1.CryptoKeyName;
21+
import com.google.cloud.kms.v1.DecryptRequest;
2122
import com.google.cloud.kms.v1.DecryptResponse;
2223
import com.google.cloud.kms.v1.KeyManagementServiceClient;
24+
import com.google.common.hash.HashCode;
25+
import com.google.common.hash.HashFunction;
26+
import com.google.common.hash.Hashing;
2327
import com.google.protobuf.ByteString;
28+
import com.google.protobuf.Int64Value;
2429
import java.io.IOException;
2530

31+
32+
2633
public class DecryptSymmetric {
2734

2835
public void decryptSymmetric() throws IOException {
@@ -44,14 +51,40 @@ public void decryptSymmetric(
4451
// completing all of your requests, call the "close" method on the client to
4552
// safely clean up any remaining background resources.
4653
try (KeyManagementServiceClient client = KeyManagementServiceClient.create()) {
47-
// Build the key version name from the project, location, key ring, and
48-
// key.
54+
// Build the key version from the project, location, key ring, and key.
4955
CryptoKeyName keyName = CryptoKeyName.of(projectId, locationId, keyRingId, keyId);
5056

51-
// Decrypt the response.
52-
DecryptResponse response = client.decrypt(keyName, ByteString.copyFrom(ciphertext));
57+
// Optional, but recommended: compute ciphertext's CRC32C. See helpers below.
58+
long ciphertextCrc32c = getCrc32cAsLong(ciphertext);
59+
60+
// Decrypt the ciphertext.
61+
DecryptRequest request =
62+
DecryptRequest.newBuilder()
63+
.setName(keyName.toString())
64+
.setCiphertext(ByteString.copyFrom(ciphertext))
65+
.setCiphertextCrc32C(
66+
Int64Value.newBuilder().setValue(ciphertextCrc32c).build())
67+
.build();
68+
DecryptResponse response = client.decrypt(request);
69+
70+
// Optional, but recommended: perform integrity verification on response.
71+
// For more details on ensuring E2E in-transit integrity to and from Cloud KMS visit:
72+
// https://cloud.google.com/kms/docs/data-integrity-guidelines
73+
if (!crcMatches(response.getPlaintextCrc32C().getValue(),
74+
response.getPlaintext().toByteArray())) {
75+
throw new IOException("Decrypt: response from server corrupted");
76+
}
77+
5378
System.out.printf("Plaintext: %s%n", response.getPlaintext().toStringUtf8());
5479
}
5580
}
81+
82+
private long getCrc32cAsLong(byte[] data) {
83+
return Hashing.crc32c().hashBytes(data).padToLong();
84+
}
85+
86+
private boolean crcMatches(long expectedCrc, byte[] data) {
87+
return expectedCrc == getCrc32cAsLong(data);
88+
}
5689
}
5790
// [END kms_decrypt_symmetric]

kms/src/main/java/kms/EncryptSymmetric.java

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,18 @@
1818

1919
// [START kms_encrypt_symmetric]
2020
import com.google.cloud.kms.v1.CryptoKeyName;
21+
import com.google.cloud.kms.v1.EncryptRequest;
2122
import com.google.cloud.kms.v1.EncryptResponse;
2223
import com.google.cloud.kms.v1.KeyManagementServiceClient;
24+
import com.google.common.hash.HashCode;
25+
import com.google.common.hash.HashFunction;
26+
import com.google.common.hash.Hashing;
2327
import com.google.protobuf.ByteString;
28+
import com.google.protobuf.Int64Value;
2429
import java.io.IOException;
2530

31+
32+
2633
public class EncryptSymmetric {
2734

2835
public void encryptSymmetric() throws IOException {
@@ -39,19 +46,53 @@ public void encryptSymmetric() throws IOException {
3946
public void encryptSymmetric(
4047
String projectId, String locationId, String keyRingId, String keyId, String plaintext)
4148
throws IOException {
49+
4250
// Initialize client that will be used to send requests. This client only
4351
// needs to be created once, and can be reused for multiple requests. After
4452
// completing all of your requests, call the "close" method on the client to
4553
// safely clean up any remaining background resources.
4654
try (KeyManagementServiceClient client = KeyManagementServiceClient.create()) {
47-
// Build the key version name from the project, location, key ring, key,
48-
// and key version.
49-
CryptoKeyName keyVersionName = CryptoKeyName.of(projectId, locationId, keyRingId, keyId);
55+
// Build the key name from the project, location, key ring, and key.
56+
CryptoKeyName cryptoKeyName = CryptoKeyName.of(projectId, locationId, keyRingId, keyId);
57+
58+
// Convert plaintext to ByteString.
59+
ByteString plaintextByteString = ByteString.copyFromUtf8(plaintext);
60+
61+
// Optional, but recommended: compute plaintext's CRC32C. See helper below.
62+
long plaintextCrc32c = getCrc32cAsLong(plaintextByteString.toByteArray());
5063

5164
// Encrypt the plaintext.
52-
EncryptResponse response = client.encrypt(keyVersionName, ByteString.copyFromUtf8(plaintext));
65+
EncryptRequest request = EncryptRequest.newBuilder()
66+
.setName(cryptoKeyName.toString())
67+
.setPlaintext(plaintextByteString)
68+
.setPlaintextCrc32C(
69+
Int64Value.newBuilder().setValue(plaintextCrc32c).build())
70+
.build();
71+
EncryptResponse response = client.encrypt(request);
72+
73+
// Optional, but recommended: perform integrity verification on response.
74+
// For more details on ensuring E2E in-transit integrity to and from Cloud KMS visit:
75+
// https://cloud.google.com/kms/docs/data-integrity-guidelines
76+
if (!response.getVerifiedPlaintextCrc32C()) {
77+
throw new IOException("Encrypt: request to server corrupted");
78+
}
79+
80+
// See helper below.
81+
if (!crcMatches(response.getCiphertextCrc32C().getValue(),
82+
response.getCiphertext().toByteArray())) {
83+
throw new IOException("Encrypt: response from server corrupted");
84+
}
85+
5386
System.out.printf("Ciphertext: %s%n", response.getCiphertext().toStringUtf8());
5487
}
5588
}
89+
90+
private long getCrc32cAsLong(byte[] data) {
91+
return Hashing.crc32c().hashBytes(data).padToLong();
92+
}
93+
94+
private boolean crcMatches(long expectedCrc, byte[] data) {
95+
return expectedCrc == getCrc32cAsLong(data);
96+
}
5697
}
5798
// [END kms_encrypt_symmetric]

kms/src/main/java/kms/GetPublicKey.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020
import com.google.cloud.kms.v1.CryptoKeyVersionName;
2121
import com.google.cloud.kms.v1.KeyManagementServiceClient;
2222
import com.google.cloud.kms.v1.PublicKey;
23+
import com.google.common.hash.HashCode;
24+
import com.google.common.hash.HashFunction;
25+
import com.google.common.hash.Hashing;
26+
import com.google.protobuf.Int64Value;
2327
import java.io.IOException;
2428
import java.security.GeneralSecurityException;
2529

@@ -51,8 +55,30 @@ public void getPublicKey(
5155

5256
// Get the public key.
5357
PublicKey publicKey = client.getPublicKey(keyVersionName);
58+
59+
// Optional, but recommended: perform integrity verification on response.
60+
// For more details on ensuring E2E in-transit integrity to and from Cloud KMS visit:
61+
// https://cloud.google.com/kms/docs/data-integrity-guidelines
62+
if (!publicKey.getName().equals(keyVersionName.toString())) {
63+
throw new IOException("GetPublicKey: request to server corrupted");
64+
}
65+
66+
// See helper below.
67+
if (!crcMatches(publicKey.getPemCrc32C().getValue(),
68+
publicKey.getPemBytes().toByteArray())) {
69+
throw new IOException("GetPublicKey: response from server corrupted");
70+
}
71+
5472
System.out.printf("Public key: %s%n", publicKey.getPem());
5573
}
5674
}
75+
76+
private long getCrc32cAsLong(byte[] data) {
77+
return Hashing.crc32c().hashBytes(data).padToLong();
78+
}
79+
80+
private boolean crcMatches(long expectedCrc, byte[] data) {
81+
return expectedCrc == getCrc32cAsLong(data);
82+
}
5783
}
5884
// [END kms_get_public_key]

kms/src/main/java/kms/SignAsymmetric.java

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,16 @@
1717
package kms;
1818

1919
// [START kms_sign_asymmetric]
20+
import com.google.cloud.kms.v1.AsymmetricSignRequest;
2021
import com.google.cloud.kms.v1.AsymmetricSignResponse;
2122
import com.google.cloud.kms.v1.CryptoKeyVersionName;
2223
import com.google.cloud.kms.v1.Digest;
2324
import com.google.cloud.kms.v1.KeyManagementServiceClient;
25+
import com.google.common.hash.HashCode;
26+
import com.google.common.hash.HashFunction;
27+
import com.google.common.hash.Hashing;
2428
import com.google.protobuf.ByteString;
29+
import com.google.protobuf.Int64Value;
2530
import java.io.IOException;
2631
import java.nio.charset.StandardCharsets;
2732
import java.security.GeneralSecurityException;
@@ -70,14 +75,44 @@ public void signAsymmetric(
7075
// Build the digest object.
7176
Digest digest = Digest.newBuilder().setSha256(ByteString.copyFrom(hash)).build();
7277

78+
// Optional, but recommended: compute digest's CRC32C. See helper below.
79+
long digestCrc32c = getCrc32cAsLong(hash);
80+
7381
// Sign the digest.
74-
AsymmetricSignResponse result = client.asymmetricSign(keyVersionName, digest);
82+
AsymmetricSignRequest request =
83+
AsymmetricSignRequest.newBuilder()
84+
.setName(keyVersionName.toString())
85+
.setDigest(digest)
86+
.setDigestCrc32C(Int64Value.newBuilder().setValue(digestCrc32c).build())
87+
.build();
88+
AsymmetricSignResponse response = client.asymmetricSign(request);
89+
90+
// Optional, but recommended: perform integrity verification on response.
91+
// For more details on ensuring E2E in-transit integrity to and from Cloud KMS visit:
92+
// https://cloud.google.com/kms/docs/data-integrity-guidelines
93+
if (!response.getVerifiedDigestCrc32C()) {
94+
throw new IOException("AsymmetricSign: request to server corrupted");
95+
}
96+
97+
// See helper below.
98+
if (!crcMatches(response.getSignatureCrc32C().getValue(),
99+
response.getSignature().toByteArray())) {
100+
throw new IOException("AsymmetricSign: response from server corrupted");
101+
}
75102

76103
// Get the signature.
77-
byte[] signature = result.getSignature().toByteArray();
104+
byte[] signature = response.getSignature().toByteArray();
78105

79106
System.out.printf("Signature %s%n", signature);
80107
}
81108
}
109+
110+
private long getCrc32cAsLong(byte[] data) {
111+
return Hashing.crc32c().hashBytes(data).padToLong();
112+
}
113+
114+
private boolean crcMatches(long expectedCrc, byte[] data) {
115+
return expectedCrc == getCrc32cAsLong(data);
116+
}
82117
}
83118
// [END kms_sign_asymmetric]

0 commit comments

Comments
 (0)