From dbd6867f9d113e8e957ed0db5a851d36dfb76ede Mon Sep 17 00:00:00 2001 From: Tink Team Date: Thu, 30 Nov 2023 09:50:09 -0800 Subject: [PATCH] feat: Support GRPC-based KeyManagementServiceClient in the GCP-KMS AEAD. PiperOrigin-RevId: 586704028 Change-Id: I20bd7125d2dea17b91b47b9627512f2b23b8ab37 --- maven/tink-java-gcpkms.pom.xml | 18 +++ .../tink/integration/gcpkms/BUILD.bazel | 6 + .../tink/integration/gcpkms/GcpKmsAead.java | 143 +++++++++++++++++- tink_java_gcpkms_deps.bzl | 3 + 4 files changed, 167 insertions(+), 3 deletions(-) diff --git a/maven/tink-java-gcpkms.pom.xml b/maven/tink-java-gcpkms.pom.xml index 84c30bf..f7f5cf2 100644 --- a/maven/tink-java-gcpkms.pom.xml +++ b/maven/tink-java-gcpkms.pom.xml @@ -85,6 +85,8 @@ 1.8 2.2.0 + 2.36.0 + 0.124.0 v1-rev20221107-2.0.0 2.31.0 1.20.0 @@ -94,6 +96,7 @@ 1.43.3 1.43.3 1.34.1 + 3.24.4 1.11.0 @@ -148,6 +151,21 @@ google-oauth-client ${google-oauth-client.version} + + com.google.api + gax + ${google-api-gax.version} + + + com.google.api.grpc + proto-google-cloud-kms-v1 + ${google-api-grpc-proto-cloud-kms-v1.version} + + + com.google.protobuf + protobuf-java + ${google-protobuf-java.version} + com.google.crypto.tink tink diff --git a/src/main/java/com/google/crypto/tink/integration/gcpkms/BUILD.bazel b/src/main/java/com/google/crypto/tink/integration/gcpkms/BUILD.bazel index 9124154..629ce76 100644 --- a/src/main/java/com/google/crypto/tink/integration/gcpkms/BUILD.bazel +++ b/src/main/java/com/google/crypto/tink/integration/gcpkms/BUILD.bazel @@ -30,7 +30,13 @@ java_library( srcs = ["GcpKmsAead.java"], deps = [ "@tink_java//src/main/java/com/google/crypto/tink:aead", + "@maven//:com_google_api_gax", + "@maven//:com_google_api_grpc_proto_google_cloud_kms_v1", "@maven//:com_google_apis_google_api_services_cloudkms", + "@maven//:com_google_cloud_google_cloud_kms", + "@maven//:com_google_code_findbugs_jsr305", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_protobuf_protobuf_java", ], ) diff --git a/src/main/java/com/google/crypto/tink/integration/gcpkms/GcpKmsAead.java b/src/main/java/com/google/crypto/tink/integration/gcpkms/GcpKmsAead.java index 56127cb..a56157d 100644 --- a/src/main/java/com/google/crypto/tink/integration/gcpkms/GcpKmsAead.java +++ b/src/main/java/com/google/crypto/tink/integration/gcpkms/GcpKmsAead.java @@ -16,17 +16,23 @@ package com.google.crypto.tink.integration.gcpkms; +import com.google.api.gax.rpc.ApiException; import com.google.api.services.cloudkms.v1.CloudKMS; import com.google.api.services.cloudkms.v1.model.DecryptRequest; import com.google.api.services.cloudkms.v1.model.DecryptResponse; import com.google.api.services.cloudkms.v1.model.EncryptRequest; import com.google.api.services.cloudkms.v1.model.EncryptResponse; +import com.google.cloud.kms.v1.KeyManagementServiceClient; import com.google.crypto.tink.Aead; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.protobuf.ByteString; import java.io.IOException; import java.security.GeneralSecurityException; +import java.util.regex.Pattern; +import javax.annotation.Nullable; /** - * A {@link Aead} that forwards encryption/decryption requests to a key in Google Cloud KMS. * *

As of August 2017, Google Cloud KMS supports only AES-256-GCM keys. @@ -34,8 +40,7 @@ * @since 1.0.0 */ public final class GcpKmsAead implements Aead { - - /** This client knows how to talk to Google Cloud KMS. */ + /** An HTTP-based client to communicate with Google Cloud KMS. */ private final CloudKMS kmsClient; // The location of a CryptoKey in Google Cloud KMS. @@ -101,4 +106,136 @@ private static byte[] toNonNullableByteArray(byte[] data) { return data; } } + + /** + * An {@link Aead} that forwards encryption/decryption requests to a key in Google Cloud KMS using GRPC. + */ + private static final class GcpKmsAeadGrpc implements Aead { + + /** A GRPC-based client to communicate with Google Cloud KMS. */ + private final KeyManagementServiceClient kmsClient; + + // The location of a CryptoKey in Google Cloud KMS. + // Valid values have this format: projects/*/locations/*/keyRings/*/cryptoKeys/*. + // See https://cloud.google.com/kms/docs/object-hierarchy. + private final String keyName; + + private GcpKmsAeadGrpc(KeyManagementServiceClient kmsClient, String keyName) { + this.kmsClient = kmsClient; + this.keyName = keyName; + } + + @Override + public byte[] encrypt(final byte[] plaintext, final byte[] associatedData) + throws GeneralSecurityException { + try { + com.google.cloud.kms.v1.EncryptRequest encryptRequest = + com.google.cloud.kms.v1.EncryptRequest.newBuilder() + .setName(keyName) + .setPlaintext(ByteString.copyFrom(plaintext)) + .setAdditionalAuthenticatedData(ByteString.copyFrom(associatedData)) + .build(); + + com.google.cloud.kms.v1.EncryptResponse encResponse = kmsClient.encrypt(encryptRequest); + return encResponse.getCiphertext().toByteArray(); + } catch (ApiException e) { + throw new GeneralSecurityException("encryption failed", e); + } + } + + @Override + public byte[] decrypt(final byte[] ciphertext, final byte[] associatedData) + throws GeneralSecurityException { + try { + com.google.cloud.kms.v1.DecryptRequest decryptRequest = + com.google.cloud.kms.v1.DecryptRequest.newBuilder() + .setName(keyName) + .setCiphertext(ByteString.copyFrom(ciphertext)) + .setAdditionalAuthenticatedData(ByteString.copyFrom(associatedData)) + .build(); + + com.google.cloud.kms.v1.DecryptResponse decResponse = kmsClient.decrypt(decryptRequest); + return decResponse.getPlaintext().toByteArray(); + } catch (ApiException e) { + throw new GeneralSecurityException("decryption failed", e); + } + } + } + + /** + * A Builder to create an Aead backed by GCP Cloud KMS. + * + *

If {@link #setKeyManagementServiceClient} is used, the Aead will communicate with Cloud KMS + * via gRPC given a {@link KeyManagementServiceClient} instance. If {@link #setCloudKms} is used, + * the Aead will communicate with Cloud KMS via HTTP given a {@link CloudKMS} instance. + * + *

For new users we recommend using {@link #setKeyManagementServiceClient}. + */ + public static final class Builder { + @Nullable private String keyName = null; + @Nullable private CloudKMS kmsClientHttp = null; + @Nullable private KeyManagementServiceClient kmsClientGrpc = null; + private static final String KEY_NAME_PATTERN = + "projects/([^/]+)/locations/([a-zA-Z0-9_-]{1,63})/keyRings/" + + "[a-zA-Z0-9_-]{1,63}/cryptoKeys/[a-zA-Z0-9_-]{1,63}"; + private static final Pattern KEY_NAME_MATCHER = Pattern.compile(KEY_NAME_PATTERN); + + private Builder() {} + + /** Set the ResourceName of the KMS key. */ + @CanIgnoreReturnValue + public Builder setKeyName(String keyName) { + this.keyName = keyName; + return this; + } + + /** Set the CloudKms object. */ + @CanIgnoreReturnValue + public Builder setCloudKms(CloudKMS cloudKms) { + this.kmsClientHttp = cloudKms; + return this; + } + + /** Set the KeyManagementServiceClient object. */ + @CanIgnoreReturnValue + public Builder setKeyManagementServiceClient(KeyManagementServiceClient kmsClient) { + this.kmsClientGrpc = kmsClient; + return this; + } + + public Aead build() throws GeneralSecurityException { + if (keyName == null) { + throw new GeneralSecurityException("The keyName is null."); + } + + if (keyName.isEmpty()) { + throw new GeneralSecurityException("The keyName is empty."); + } + + if (!KEY_NAME_MATCHER.matcher(keyName).matches()) { + throw new GeneralSecurityException("The keyName must follow " + KEY_NAME_PATTERN); + } + + if (kmsClientGrpc == null && kmsClientHttp == null) { + throw new GeneralSecurityException( + "Either the CloudKMS or the KeyManagementServiceClient object must be provided."); + } + + if (kmsClientGrpc != null && kmsClientHttp != null) { + throw new GeneralSecurityException( + "Either the CloudKMS or the KeyManagementServiceClient object must be provided."); + } + + if (kmsClientHttp != null) { + return new GcpKmsAead(kmsClientHttp, keyName); + } + + return new GcpKmsAeadGrpc(kmsClientGrpc, keyName); + } + } + + public static Builder builder() { + return new Builder(); + } } diff --git a/tink_java_gcpkms_deps.bzl b/tink_java_gcpkms_deps.bzl index 80cd940..29315b7 100644 --- a/tink_java_gcpkms_deps.bzl +++ b/tink_java_gcpkms_deps.bzl @@ -14,6 +14,8 @@ TINK_JAVA_GCPKMS_MAVEN_TOOLS_ARTIFACTS = [ ] TINK_JAVA_GCPKMS_MAVEN_ARTIFACTS = [ + "com.google.api:gax:2.36.0", + "com.google.api.grpc:proto-google-cloud-kms-v1:0.124.0", "com.google.api-client:google-api-client:2.2.0", "com.google.apis:google-api-services-cloudkms:v1-rev20221107-2.0.0", "com.google.auth:google-auth-library-oauth2-http:1.20.0", @@ -26,6 +28,7 @@ TINK_JAVA_GCPKMS_MAVEN_ARTIFACTS = [ "com.google.http-client:google-http-client-gson:1.43.3", "com.google.http-client:google-http-client:1.43.3", "com.google.oauth-client:google-oauth-client:1.34.1", + "com.google.protobuf:protobuf-java:3.24.4", ] def tink_java_gcpkms_deps():