All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.cloud.broker.encryption.backends.CloudKMSBackend Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.cloud.broker.encryption.backends;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.security.GeneralSecurityException;

import com.google.api.client.googleapis.util.Utils;
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.auth.http.HttpCredentialsAdapter;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.cloud.broker.encryption.backends.keyset.KeysetManager;
import com.google.crypto.tink.Aead;
import com.google.crypto.tink.KeysetHandle;
import com.google.crypto.tink.KeysetWriter;
import com.google.crypto.tink.aead.AeadConfig;
import com.google.crypto.tink.aead.AeadKeyTemplates;
import com.google.crypto.tink.proto.KeyTemplate;

import com.google.cloud.broker.settings.AppSettings;
import com.google.cloud.broker.utils.Constants;
import com.google.cloud.broker.encryption.backends.keyset.KeysetUtils;
import com.google.cloud.broker.checks.CheckResult;

/**
 * EnvelopeEncryptionBackend uses a static key stored in Cloud Storage or the local filesystem.
 * Cloud KMS is called once at startup to decrypt the static key.
 * This is the preferred encryption backend when high-throughput is a requirement.
 */
public class CloudKMSBackend extends AbstractEncryptionBackend {

    private static final String KMS_API = "https://www.googleapis.com/auth/cloudkms";

    static {
        try {
            AeadConfig.register();
        } catch (GeneralSecurityException e) {
            throw new RuntimeException("Failed to register Tink Aead",e);
        }
    }
    private Aead aead;
    private static KeyTemplate KEY_TEMPLATE = AeadKeyTemplates.AES256_GCM;

    private Aead getAead() {
        if (aead == null) {
            String kekUri = AppSettings.getInstance().getString(AppSettings.ENCRYPTION_KEK_URI);
            String dekUri = AppSettings.getInstance().getString(AppSettings.ENCRYPTION_DEK_URI);
            try {
                CloudKMS kmsClient = getKMSClient();
                aead = readKeyset(dekUri, kekUri, kmsClient).getPrimitive(Aead.class);
            } catch (GeneralSecurityException e) {
                throw new RuntimeException("Failed to initialize encryption backend", e);
            }
        }
        return aead;
    }

    @Override
    public byte[] decrypt(byte[] cipherText) {
        try {
            return getAead().decrypt(cipherText, null);
        } catch (GeneralSecurityException e){
            throw new RuntimeException(e);
        }
    }

    @Override
    public byte[] encrypt(byte[] plainText) {
        try {
            return getAead().encrypt(plainText, null);
        } catch (GeneralSecurityException e){
            throw new RuntimeException(e);
        }
    }

    @Override
    public CheckResult checkConnection() {
        try {
            encrypt("ABCDEFGH".getBytes());
            return new CheckResult(true);
        } catch(Exception e) {
            StringWriter sw = new StringWriter();
            e.printStackTrace(new PrintWriter(sw));
            return new CheckResult(false, sw.toString());
        }
    }

    private static CloudKMS getKMSClient() {
        GoogleCredentials credentials;
        try {
            credentials = GoogleCredentials.getApplicationDefault().createScoped(KMS_API);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return new CloudKMS.Builder(
            Utils.getDefaultTransport(), Utils.getDefaultJsonFactory(), new HttpCredentialsAdapter(credentials)
        ).setApplicationName(Constants.APPLICATION_NAME).build();
    }

    private static KeysetHandle readKeyset(String dekUri, String kekUri, CloudKMS kmsClient) {
        try {
            Aead kek = new GcpKmsAead(kmsClient, kekUri);
            KeysetManager keysetReader = KeysetUtils.getKeysetManager(dekUri);
            return KeysetHandle.read(keysetReader, kek);
        } catch (IOException | GeneralSecurityException e) {
            throw new RuntimeException("Failed to read Keyset `" + dekUri + "` with KMS key `" + kekUri + "`", e);
        }
    }

    public static KeysetHandle generateAndWriteKeyset(String dekUri, String keyUri) {
        return generateAndWriteKeyset(KEY_TEMPLATE, dekUri, keyUri, getKMSClient());
    }

    private static KeysetHandle generateAndWriteKeyset(KeyTemplate keyTemplate, String dekUri, String kekUri, CloudKMS kmsClient) {
        try {
            Aead aead = new GcpKmsAead(kmsClient, kekUri);
            KeysetHandle keysetHandle = KeysetHandle.generateNew(keyTemplate);
            KeysetWriter keysetWriter = KeysetUtils.getKeysetManager(dekUri);
            keysetHandle.write(keysetWriter, aead);
            return keysetHandle;
        } catch (IOException | GeneralSecurityException e) {
            throw new RuntimeException("Failed to write Keyset `" + dekUri + "` with KMS key `" + kekUri + "`", e);
        }
    }


    public static final class GcpKmsAead implements Aead {

        private final CloudKMS kmsClient;

        // The location of a key encryption key (KEK) 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 kekUri;

        GcpKmsAead(CloudKMS kmsClient, String kekUri) throws GeneralSecurityException {
            this.kmsClient = kmsClient;
            this.kekUri = kekUri;
        }

        @Override
        public byte[] encrypt(final byte[] plaintext, final byte[] aad) throws GeneralSecurityException {
            try {
                EncryptRequest request =
                    new EncryptRequest().encodePlaintext(plaintext).encodeAdditionalAuthenticatedData(aad);
                EncryptResponse response =
                    this.kmsClient
                        .projects()
                        .locations()
                        .keyRings()
                        .cryptoKeys()
                        .encrypt(this.kekUri, request)
                        .execute();
                return response.decodeCiphertext();
            } catch (IOException e) {
                throw new GeneralSecurityException("encryption failed", e);
            }
        }

        @Override
        public byte[] decrypt(final byte[] ciphertext, final byte[] aad) throws GeneralSecurityException {
            try {
                DecryptRequest request =
                    new DecryptRequest().encodeCiphertext(ciphertext).encodeAdditionalAuthenticatedData(aad);
                DecryptResponse response =
                    this.kmsClient
                        .projects()
                        .locations()
                        .keyRings()
                        .cryptoKeys()
                        .decrypt(this.kekUri, request)
                        .execute();
                return response.decodePlaintext();
            } catch (IOException e) {
                throw new GeneralSecurityException("decryption failed", e);
            }
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy