diff --git a/java/google/registry/keyring/kms/BUILD b/java/google/registry/keyring/kms/BUILD index e5afcdc24..ce7760fb1 100644 --- a/java/google/registry/keyring/kms/BUILD +++ b/java/google/registry/keyring/kms/BUILD @@ -15,6 +15,7 @@ java_library( "//third_party/java/objectify:objectify-v4_1", "@com_google_api_client", "@com_google_apis_google_api_services_cloudkms", + "@com_google_auto_value", "@com_google_code_findbugs_jsr305", "@com_google_dagger", "@com_google_guava", diff --git a/java/google/registry/keyring/kms/EncryptResponse.java b/java/google/registry/keyring/kms/EncryptResponse.java new file mode 100644 index 000000000..c67ae1b7d --- /dev/null +++ b/java/google/registry/keyring/kms/EncryptResponse.java @@ -0,0 +1,41 @@ +// Copyright 2017 The Nomulus Authors. All Rights Reserved. +// +// 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 google.registry.keyring.kms; + +import com.google.auto.value.AutoValue; +import com.google.common.annotations.VisibleForTesting; + +/** + * A value type class containing a Cloud KMS encrypted and encoded ciphertext, and the name of the + * CryptoKeyVersion used to encrypt it. + */ +@AutoValue +abstract class EncryptResponse { + + static EncryptResponse create( + com.google.api.services.cloudkms.v1beta1.model.EncryptResponse cloudKmsEncryptResponse) { + return new AutoValue_EncryptResponse( + cloudKmsEncryptResponse.getCiphertext(), cloudKmsEncryptResponse.getName()); + } + + @VisibleForTesting + static EncryptResponse create(String ciphertext, String cryptoKeyVersionName) { + return new AutoValue_EncryptResponse(ciphertext, cryptoKeyVersionName); + } + + abstract String ciphertext(); + + abstract String cryptoKeyVersionName(); +} diff --git a/java/google/registry/keyring/kms/KmsConnection.java b/java/google/registry/keyring/kms/KmsConnection.java new file mode 100644 index 000000000..b92e0e8f8 --- /dev/null +++ b/java/google/registry/keyring/kms/KmsConnection.java @@ -0,0 +1,42 @@ +// Copyright 2017 The Nomulus Authors. All Rights Reserved. +// +// 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 google.registry.keyring.kms; + +import java.io.IOException; + +/** An abstraction to simplify Cloud KMS operations. */ +interface KmsConnection { + + /** + * The maximum allowable secret size, as set by Cloud KMS. + * + * @see projects.locations.keyRings.cryptoKeys.encrypt + */ + int MAX_SECRET_SIZE_BYTES = 64 * 1024; + + /** + * Encrypts a plaintext with CryptoKey {@code cryptoKeyName} on KeyRing {@code keyRingName}. + * + *

The latest CryptoKeyVersion is used to encrypt the value. The value must not be larger than + * {@code MAX_SECRET_SIZE_BYTES}. + * + *

If no applicable CryptoKey or CryptoKeyVersion exist, they will be created. + */ + EncryptResponse encrypt(String cryptoKeyName, byte[] plaintext) throws IOException; + + /** Decrypts a Cloud KMS encrypted and encoded value with CryptoKey {@code cryptoKeyName}. */ + byte[] decrypt(String cryptoKeyName, String encodedCiphertext) throws IOException; +} diff --git a/java/google/registry/keyring/kms/KmsConnectionImpl.java b/java/google/registry/keyring/kms/KmsConnectionImpl.java new file mode 100644 index 000000000..ec9f67054 --- /dev/null +++ b/java/google/registry/keyring/kms/KmsConnectionImpl.java @@ -0,0 +1,147 @@ +// Copyright 2017 The Nomulus Authors. All Rights Reserved. +// +// 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 google.registry.keyring.kms; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.api.client.googleapis.json.GoogleJsonResponseException; +import com.google.api.client.http.HttpStatusCodes; +import com.google.api.services.cloudkms.v1beta1.CloudKMS; +import com.google.api.services.cloudkms.v1beta1.model.CryptoKey; +import com.google.api.services.cloudkms.v1beta1.model.CryptoKeyVersion; +import com.google.api.services.cloudkms.v1beta1.model.DecryptRequest; +import com.google.api.services.cloudkms.v1beta1.model.EncryptRequest; +import com.google.api.services.cloudkms.v1beta1.model.KeyRing; +import google.registry.config.RegistryConfig.Config; +import google.registry.keyring.api.KeyringException; +import java.io.IOException; +import javax.inject.Inject; + +/** The {@link KmsConnection} which talks to Cloud KMS. */ +class KmsConnectionImpl implements KmsConnection { + + private static final String KMS_KEYRING_NAME_FORMAT = "projects/%s/locations/global/keyRings/%s"; + private static final String KMS_CRYPTO_KEY_NAME_FORMAT = + "projects/%s/locations/global/keyRings/%s/cryptoKeys/%s"; + private static final String KMS_CRYPTO_KEY_VERSION_NAME_FORMAT = + "projects/%s/locations/global/keyRings/%s/cryptoKeys/%s/cryptoKeyVersions"; + + private final CloudKMS kms; + private final String kmsKeyRingName; + private final String projectId; + + @Inject + KmsConnectionImpl( + @Config("cloudKmsProjectId") String projectId, + @Config("cloudKmsKeyRing") String kmsKeyringName, + CloudKMS kms) { + this.projectId = projectId; + this.kmsKeyRingName = kmsKeyringName; + this.kms = kms; + } + + @Override + public EncryptResponse encrypt(String cryptoKeyName, byte[] value) throws IOException { + checkArgument( + value.length <= MAX_SECRET_SIZE_BYTES, + "Value to encrypt was larger than %s bytes", + MAX_SECRET_SIZE_BYTES); + String fullKeyRingName = getKeyRingName(projectId, kmsKeyRingName); + try { + kms.projects().locations().keyRings().get(fullKeyRingName).execute(); + } catch (GoogleJsonResponseException jsonException) { + if (jsonException.getStatusCode() == HttpStatusCodes.STATUS_CODE_NOT_FOUND) { + // Create the KeyRing in the "global" namespace. Encryption keys will be accessible from all + // GCP regions. + kms.projects() + .locations() + .keyRings() + .create("global", new KeyRing().setName(fullKeyRingName)) + .execute(); + } else { + throw jsonException; + } + } + + String fullKeyName = getCryptoKeyName(projectId, kmsKeyRingName, cryptoKeyName); + + try { + kms.projects().locations().keyRings().cryptoKeys().get(fullKeyName).execute(); + } catch (GoogleJsonResponseException jsonException) { + if (jsonException.getStatusCode() == HttpStatusCodes.STATUS_CODE_NOT_FOUND) { + kms.projects() + .locations() + .keyRings() + .cryptoKeys() + .create( + fullKeyName, new CryptoKey().setName(cryptoKeyName).setPurpose("ENCRYPT_DECRYPT")) + .execute(); + } else { + throw jsonException; + } + } + + CryptoKeyVersion cryptoKeyVersion = + kms.projects() + .locations() + .keyRings() + .cryptoKeys() + .cryptoKeyVersions() + .create( + getCryptoKeyVersionName(projectId, kmsKeyRingName, cryptoKeyName), + new CryptoKeyVersion()) + .execute(); + + return EncryptResponse.create( + kms.projects() + .locations() + .keyRings() + .cryptoKeys() + .encrypt(cryptoKeyVersion.getName(), new EncryptRequest().encodePlaintext(value)) + .execute()); + } + + @Override + public byte[] decrypt(String cryptoKeyName, String encodedCiphertext) { + try { + return kms.projects() + .locations() + .keyRings() + .cryptoKeys() + .decrypt( + getCryptoKeyName(projectId, kmsKeyRingName, cryptoKeyName), + new DecryptRequest().setCiphertext(encodedCiphertext)) + .execute() + .decodePlaintext(); + } catch (IOException e) { + throw new KeyringException( + String.format("CloudKMS decrypt operation failed for secret %s", cryptoKeyName), e); + } + } + + static String getKeyRingName(String projectId, String kmsKeyRingName) { + return String.format(KMS_KEYRING_NAME_FORMAT, projectId, kmsKeyRingName); + } + + static String getCryptoKeyName(String projectId, String kmsKeyRingName, String cryptoKeyName) { + return String.format(KMS_CRYPTO_KEY_NAME_FORMAT, projectId, kmsKeyRingName, cryptoKeyName); + } + + static String getCryptoKeyVersionName( + String projectId, String kmsKeyRingName, String cryptoKeyName) { + return String.format( + KMS_CRYPTO_KEY_VERSION_NAME_FORMAT, projectId, kmsKeyRingName, cryptoKeyName); + } +} diff --git a/java/google/registry/keyring/kms/KmsKeyring.java b/java/google/registry/keyring/kms/KmsKeyring.java index d638064e8..7de1e09d2 100644 --- a/java/google/registry/keyring/kms/KmsKeyring.java +++ b/java/google/registry/keyring/kms/KmsKeyring.java @@ -20,10 +20,7 @@ import static google.registry.model.common.EntityGroupRoot.getCrossTldKey; import static google.registry.model.ofy.ObjectifyService.ofy; import static java.nio.charset.StandardCharsets.UTF_8; -import com.google.api.services.cloudkms.v1beta1.CloudKMS; -import com.google.api.services.cloudkms.v1beta1.model.DecryptRequest; import com.googlecode.objectify.Key; -import google.registry.config.RegistryConfig.Config; import google.registry.keyring.api.Keyring; import google.registry.keyring.api.KeyringException; import google.registry.keyring.api.PgpHelper; @@ -52,12 +49,6 @@ import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider; */ public class KmsKeyring implements Keyring { - private static final String KMS_KEYRING_NAME_FORMAT = "projects/%s/locations/global/keyRings/%s"; - private static final String KMS_CRYPTO_KEY_NAME_FORMAT = - "projects/%s/locations/global/keyRings/%s/cryptoKeys/%s"; - private static final String KMS_CRYPTO_KEY_VERSION_NAME_FORMAT = - "projects/%s/locations/global/keyRings/%s/cryptoKeys/%s/cryptoKeyVersions"; - static final String BRAINTREE_PRIVATE_KEY_NAME = "braintree-private-key"; static final String BRDA_RECEIVER_PUBLIC_NAME = "brda-receiver-public"; static final String BRDA_SIGNING_PRIVATE_NAME = "brda-signing-private"; @@ -75,18 +66,11 @@ public class KmsKeyring implements Keyring { static final String RDE_STAGING_PRIVATE_NAME = "rde-staging-private"; static final String RDE_STAGING_PUBLIC_NAME = "rde-staging-public"; - private final CloudKMS kms; - private final String kmsKeyRingName; - private final String projectId; + private final KmsConnection kmsConnection; @Inject - KmsKeyring( - @Config("cloudKmsProjectId") String projectId, - @Config("cloudKmsKeyRing") String kmsKeyringName, - CloudKMS kms) { - this.projectId = projectId; - this.kmsKeyRingName = kmsKeyringName; - this.kms = kms; + KmsKeyring(KmsConnection kmsConnection) { + this.kmsConnection = kmsConnection; } @Override @@ -212,32 +196,10 @@ public class KmsKeyring implements Keyring { String encryptedData = ofy().load().key(secret.getLatestRevision()).now().getEncryptedValue(); try { - return kms.projects() - .locations() - .keyRings() - .cryptoKeys() - .decrypt( - getCryptoKeyName(projectId, kmsKeyRingName, secret.getName()), - new DecryptRequest().setCiphertext(encryptedData)) - .execute() - .decodePlaintext(); + return kmsConnection.decrypt(secret.getName(), encryptedData); } catch (IOException e) { throw new KeyringException( String.format("CloudKMS decrypt operation failed for secret %s", keyName), e); } } - - static String getKeyRingName(String projectId, String kmsKeyRingName) { - return String.format(KMS_KEYRING_NAME_FORMAT, projectId, kmsKeyRingName); - } - - static String getCryptoKeyName(String projectId, String kmsKeyRingName, String cryptoKeyName) { - return String.format(KMS_CRYPTO_KEY_NAME_FORMAT, projectId, kmsKeyRingName, cryptoKeyName); - } - - static String getCryptoKeyVersionName( - String projectId, String kmsKeyRingName, String cryptoKeyName) { - return String.format( - KMS_CRYPTO_KEY_VERSION_NAME_FORMAT, projectId, kmsKeyRingName, cryptoKeyName); - } } diff --git a/java/google/registry/keyring/kms/KmsModule.java b/java/google/registry/keyring/kms/KmsModule.java index 877bcfb83..17576b17d 100644 --- a/java/google/registry/keyring/kms/KmsModule.java +++ b/java/google/registry/keyring/kms/KmsModule.java @@ -39,4 +39,9 @@ public final class KmsModule { .setApplicationName(projectId) .build(); } + + @Provides + static KmsConnection provideKmsAdapter(KmsConnectionImpl kmsAdapter) { + return kmsAdapter; + } } diff --git a/java/google/registry/keyring/kms/KmsUpdater.java b/java/google/registry/keyring/kms/KmsUpdater.java index 8dc8d51f5..af80653e9 100644 --- a/java/google/registry/keyring/kms/KmsUpdater.java +++ b/java/google/registry/keyring/kms/KmsUpdater.java @@ -32,23 +32,12 @@ import static google.registry.keyring.kms.KmsKeyring.RDE_SSH_CLIENT_PRIVATE_NAME import static google.registry.keyring.kms.KmsKeyring.RDE_SSH_CLIENT_PUBLIC_NAME; import static google.registry.keyring.kms.KmsKeyring.RDE_STAGING_PRIVATE_NAME; import static google.registry.keyring.kms.KmsKeyring.RDE_STAGING_PUBLIC_NAME; -import static google.registry.keyring.kms.KmsKeyring.getCryptoKeyName; -import static google.registry.keyring.kms.KmsKeyring.getCryptoKeyVersionName; -import static google.registry.keyring.kms.KmsKeyring.getKeyRingName; import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.util.PreconditionsUtils.checkArgumentNotNull; import static java.nio.charset.StandardCharsets.UTF_8; -import com.google.api.client.googleapis.json.GoogleJsonResponseException; -import com.google.api.services.cloudkms.v1beta1.CloudKMS; -import com.google.api.services.cloudkms.v1beta1.model.CryptoKey; -import com.google.api.services.cloudkms.v1beta1.model.CryptoKeyVersion; -import com.google.api.services.cloudkms.v1beta1.model.EncryptRequest; -import com.google.api.services.cloudkms.v1beta1.model.EncryptResponse; -import com.google.api.services.cloudkms.v1beta1.model.KeyRing; import com.google.common.collect.ImmutableMap; import com.googlecode.objectify.VoidWork; -import google.registry.config.RegistryConfig.Config; import google.registry.model.server.KmsSecret; import google.registry.model.server.KmsSecretRevision; import java.io.IOException; @@ -65,22 +54,12 @@ import org.bouncycastle.openpgp.bc.BcPGPSecretKeyRing; */ public final class KmsUpdater { - private static final int RESOURCE_NOT_FOUND = 404; - - private final String projectId; - private final String kmsKeyRingName; - private final CloudKMS kms; - + private final KmsConnection kmsConnection; private final HashMap secretValues; @Inject - public KmsUpdater( - @Config("cloudKmsProjectId") String projectId, - @Config("cloudKmsKeyRing") String kmsKeyRingName, - CloudKMS kms) { - this.projectId = projectId; - this.kmsKeyRingName = kmsKeyRingName; - this.kms = kms; + public KmsUpdater(KmsConnection kmsConnection) { + this.kmsConnection = kmsConnection; // Use LinkedHashMap to preserve insertion order on update() to simplify testing and debugging this.secretValues = new LinkedHashMap<>(); @@ -180,64 +159,10 @@ public final class KmsUpdater { */ private ImmutableMap encryptValues(Map keyValues) throws IOException { - String fullKeyRingName = getKeyRingName(projectId, kmsKeyRingName); - try { - kms.projects().locations().keyRings().get(fullKeyRingName).execute(); - } catch (GoogleJsonResponseException jsonException) { - if (jsonException.getStatusCode() == RESOURCE_NOT_FOUND) { - // Create the KeyRing in the "global" namespace. Encryption keys will be accessible from all - // GCP regions. - kms.projects() - .locations() - .keyRings() - .create("global", new KeyRing().setName(fullKeyRingName)) - .execute(); - } else { - throw jsonException; - } - } - ImmutableMap.Builder encryptedValues = new ImmutableMap.Builder<>(); for (Map.Entry entry : keyValues.entrySet()) { - String keyName = entry.getKey(); - String fullKeyName = getCryptoKeyName(projectId, kmsKeyRingName, keyName); - - try { - kms.projects().locations().keyRings().cryptoKeys().get(fullKeyName).execute(); - } catch (GoogleJsonResponseException jsonException) { - if (jsonException.getStatusCode() == RESOURCE_NOT_FOUND) { - kms.projects() - .locations() - .keyRings() - .cryptoKeys() - .create(fullKeyName, new CryptoKey().setName(keyName).setPurpose("ENCRYPT_DECRYPT")) - .execute(); - } else { - throw jsonException; - } - } - - CryptoKeyVersion cryptoKeyVersion = - kms.projects() - .locations() - .keyRings() - .cryptoKeys() - .cryptoKeyVersions() - .create( - getCryptoKeyVersionName(projectId, kmsKeyRingName, keyName), - new CryptoKeyVersion()) - .execute(); - - encryptedValues.put( - keyName, - kms.projects() - .locations() - .keyRings() - .cryptoKeys() - .encrypt( - cryptoKeyVersion.getName(), - new EncryptRequest().encodePlaintext(entry.getValue())) - .execute()); + String secretName = entry.getKey(); + encryptedValues.put(secretName, kmsConnection.encrypt(secretName, entry.getValue())); } return encryptedValues.build(); } @@ -262,8 +187,8 @@ public final class KmsUpdater { KmsSecretRevision secretRevision = new KmsSecretRevision.Builder() - .setEncryptedValue(revisionData.getCiphertext()) - .setKmsCryptoKeyVersionName(revisionData.getName()) + .setEncryptedValue(revisionData.ciphertext()) + .setKmsCryptoKeyVersionName(revisionData.cryptoKeyVersionName()) .setParent(secretName) .build(); ofy() diff --git a/javatests/google/registry/keyring/kms/FakeKmsConnection.java b/javatests/google/registry/keyring/kms/FakeKmsConnection.java new file mode 100644 index 000000000..24e0fffa8 --- /dev/null +++ b/javatests/google/registry/keyring/kms/FakeKmsConnection.java @@ -0,0 +1,46 @@ +// Copyright 2017 The Nomulus Authors. All Rights Reserved. +// +// 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 google.registry.keyring.kms; + +import com.google.common.io.BaseEncoding; +import java.io.IOException; +import org.bouncycastle.util.Arrays; + +class FakeKmsConnection implements KmsConnection { + + FakeKmsConnection() {} + + /** + * Returns a dummy {@link EncryptResponse}. + * + *

The "encrypted value" in the response is the provided value reversed and then base64-encoded + * and the name of the cryptoKeyVersion is {@code cryptoKeyName + "/foo"}. + */ + @Override + public EncryptResponse encrypt(String cryptoKeyName, byte[] plaintext) throws IOException { + return EncryptResponse.create( + BaseEncoding.base64().encode(Arrays.reverse(plaintext)), cryptoKeyName + "/foo"); + } + + /** + * Returns a "decrypted" plaintext. + * + *

The plaintext is the encodedCiphertext base64-decoded and then reversed. + */ + @Override + public byte[] decrypt(String cryptoKeyName, String encodedCiphertext) throws IOException { + return Arrays.reverse(BaseEncoding.base64().decode(encodedCiphertext)); + } +} diff --git a/javatests/google/registry/keyring/kms/KmsConnectionImplTest.java b/javatests/google/registry/keyring/kms/KmsConnectionImplTest.java new file mode 100644 index 000000000..ddc731a13 --- /dev/null +++ b/javatests/google/registry/keyring/kms/KmsConnectionImplTest.java @@ -0,0 +1,194 @@ +// Copyright 2017 The Nomulus Authors. All Rights Reserved. +// +// 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 google.registry.keyring.kms; + +import static com.google.common.truth.Truth.assertThat; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.api.client.googleapis.json.GoogleJsonResponseException; +import com.google.api.client.http.HttpResponse; +import com.google.api.client.http.HttpResponseException; +import com.google.api.services.cloudkms.v1beta1.CloudKMS; +import com.google.api.services.cloudkms.v1beta1.model.CryptoKey; +import com.google.api.services.cloudkms.v1beta1.model.CryptoKeyVersion; +import com.google.api.services.cloudkms.v1beta1.model.DecryptRequest; +import com.google.api.services.cloudkms.v1beta1.model.DecryptResponse; +import com.google.api.services.cloudkms.v1beta1.model.EncryptRequest; +import com.google.api.services.cloudkms.v1beta1.model.EncryptResponse; +import com.google.api.services.cloudkms.v1beta1.model.KeyRing; +import java.io.ByteArrayInputStream; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class KmsConnectionImplTest { + + @Mock private CloudKMS kms; + @Mock private CloudKMS.Projects kmsProjects; + @Mock private CloudKMS.Projects.Locations kmsLocations; + @Mock private CloudKMS.Projects.Locations.KeyRings kmsKeyRings; + @Mock private CloudKMS.Projects.Locations.KeyRings.Get kmsKeyRingsGet; + @Mock private CloudKMS.Projects.Locations.KeyRings.Create kmsKeyRingsCreate; + @Mock private CloudKMS.Projects.Locations.KeyRings.CryptoKeys kmsCryptoKeys; + @Mock private CloudKMS.Projects.Locations.KeyRings.CryptoKeys.Get kmsCryptoKeysGet; + @Mock private CloudKMS.Projects.Locations.KeyRings.CryptoKeys.Create kmsCryptoKeysCreate; + + @Mock + private CloudKMS.Projects.Locations.KeyRings.CryptoKeys.CryptoKeyVersions kmsCryptoKeyVersions; + + @Mock + private CloudKMS.Projects.Locations.KeyRings.CryptoKeys.CryptoKeyVersions.Create + kmsCryptoKeyVersionsCreate; + + @Mock private CloudKMS.Projects.Locations.KeyRings.CryptoKeys.Encrypt kmsCryptoKeysEncrypt; + @Mock private CloudKMS.Projects.Locations.KeyRings.CryptoKeys.Decrypt kmsCryptoKeysDecrypt; + + @Captor private ArgumentCaptor keyRing; + @Captor private ArgumentCaptor cryptoKey; + @Captor private ArgumentCaptor cryptoKeyVersion; + @Captor private ArgumentCaptor keyRingName; + @Captor private ArgumentCaptor cryptoKeyName; + @Captor private ArgumentCaptor cryptoKeyVersionName; + @Captor private ArgumentCaptor encryptRequest; + @Captor private ArgumentCaptor decryptRequest; + + @Before + public void setUp() throws Exception { + when(kms.projects()).thenReturn(kmsProjects); + when(kmsProjects.locations()).thenReturn(kmsLocations); + when(kmsLocations.keyRings()).thenReturn(kmsKeyRings); + when(kmsKeyRings.get(anyString())).thenReturn(kmsKeyRingsGet); + when(kmsKeyRings.create(anyString(), any(KeyRing.class))).thenReturn(kmsKeyRingsCreate); + when(kmsKeyRings.cryptoKeys()).thenReturn(kmsCryptoKeys); + when(kmsCryptoKeys.get(anyString())).thenReturn(kmsCryptoKeysGet); + when(kmsCryptoKeys.create(anyString(), any(CryptoKey.class))).thenReturn(kmsCryptoKeysCreate); + when(kmsCryptoKeys.cryptoKeyVersions()).thenReturn(kmsCryptoKeyVersions); + when(kmsCryptoKeyVersions.create(anyString(), any(CryptoKeyVersion.class))) + .thenReturn(kmsCryptoKeyVersionsCreate); + when(kmsCryptoKeyVersionsCreate.execute()) + .thenReturn(new CryptoKeyVersion().setName(KmsTestHelper.DUMMY_CRYPTO_KEY_VERSION)); + when(kmsCryptoKeys.encrypt(anyString(), any(EncryptRequest.class))) + .thenReturn(kmsCryptoKeysEncrypt); + when(kmsCryptoKeysEncrypt.execute()) + .thenReturn( + new EncryptResponse() + .setName(KmsTestHelper.DUMMY_CRYPTO_KEY_VERSION) + .setCiphertext(KmsTestHelper.DUMMY_ENCRYPTED_VALUE)); + when(kmsCryptoKeys.decrypt(anyString(), any(DecryptRequest.class))) + .thenReturn(kmsCryptoKeysDecrypt); + } + + @Test + public void test_encrypt_createsKeyRingIfNotFound() throws Exception { + when(kmsKeyRingsGet.execute()).thenThrow(createNotFoundException()); + + new KmsConnectionImpl("foo", "bar", kms).encrypt("key", "moo".getBytes(UTF_8)); + + verify(kmsKeyRings).create(keyRingName.capture(), keyRing.capture()); + assertThat(keyRingName.getValue()).isEqualTo("global"); + assertThat(keyRing.getValue()) + .isEqualTo(new KeyRing().setName("projects/foo/locations/global/keyRings/bar")); + verify(kmsKeyRingsCreate).execute(); + verifyEncryptKmsApiCalls( + "moo", + "projects/foo/locations/global/keyRings/bar", + "projects/foo/locations/global/keyRings/bar/cryptoKeys/key", + "projects/foo/locations/global/keyRings/bar/cryptoKeys/key/cryptoKeyVersions"); + } + + @Test + public void test_encrypt_newCryptoKey() throws Exception { + when(kmsCryptoKeysGet.execute()).thenThrow(createNotFoundException()); + + new KmsConnectionImpl("foo", "bar", kms).encrypt("key", "moo".getBytes(UTF_8)); + + verify(kmsCryptoKeys).create(cryptoKeyName.capture(), cryptoKey.capture()); + assertThat(cryptoKeyName.getValue()) + .isEqualTo("projects/foo/locations/global/keyRings/bar/cryptoKeys/key"); + assertThat(cryptoKey.getValue()) + .isEqualTo(new CryptoKey().setName("key").setPurpose("ENCRYPT_DECRYPT")); + verify(kmsCryptoKeysCreate).execute(); + verifyEncryptKmsApiCalls( + "moo", + "projects/foo/locations/global/keyRings/bar", + "projects/foo/locations/global/keyRings/bar/cryptoKeys/key", + "projects/foo/locations/global/keyRings/bar/cryptoKeys/key/cryptoKeyVersions"); + } + + @Test + public void test_encrypt() throws Exception { + new KmsConnectionImpl("foo", "bar", kms).encrypt("key", "moo".getBytes(UTF_8)); + + verifyEncryptKmsApiCalls( + "moo", + "projects/foo/locations/global/keyRings/bar", + "projects/foo/locations/global/keyRings/bar/cryptoKeys/key", + "projects/foo/locations/global/keyRings/bar/cryptoKeys/key/cryptoKeyVersions"); + } + + @Test + public void test_decrypt() throws Exception { + when(kmsCryptoKeysDecrypt.execute()) + .thenReturn(new DecryptResponse().encodePlaintext("moo".getBytes(UTF_8))); + + byte[] plaintext = new KmsConnectionImpl("foo", "bar", kms).decrypt("key", "blah"); + + verify(kmsCryptoKeys).decrypt(cryptoKeyName.capture(), decryptRequest.capture()); + assertThat(cryptoKeyName.getValue()) + .isEqualTo("projects/foo/locations/global/keyRings/bar/cryptoKeys/key"); + assertThat(decryptRequest.getValue()).isEqualTo(new DecryptRequest().setCiphertext("blah")); + assertThat(plaintext).isEqualTo("moo".getBytes(UTF_8)); + } + + private void verifyEncryptKmsApiCalls( + String goldenValue, + String goldenCryptoKeyRingName, + String goldenCryptoKeyName, + String goldenCryptoKeyVersionName) + throws Exception { + verify(kmsKeyRings).get(keyRingName.capture()); + assertThat(keyRingName.getValue()).isEqualTo(goldenCryptoKeyRingName); + + verify(kmsCryptoKeys).get(cryptoKeyName.capture()); + assertThat(cryptoKeyName.getValue()).isEqualTo(goldenCryptoKeyName); + + verify(kmsCryptoKeyVersions).create(cryptoKeyVersionName.capture(), cryptoKeyVersion.capture()); + assertThat(cryptoKeyVersionName.getValue()).isEqualTo(goldenCryptoKeyVersionName); + + verify(kmsCryptoKeys).encrypt(cryptoKeyName.capture(), encryptRequest.capture()); + assertThat(cryptoKeyName.getValue()).isEqualTo(KmsTestHelper.DUMMY_CRYPTO_KEY_VERSION); + assertThat(encryptRequest.getValue()) + .isEqualTo(new EncryptRequest().encodePlaintext(goldenValue.getBytes(UTF_8))); + } + + private static GoogleJsonResponseException createNotFoundException() throws Exception { + ByteArrayInputStream inputStream = new ByteArrayInputStream("".getBytes(UTF_8)); + HttpResponse response = GoogleJsonResponseExceptionHelper.createHttpResponse(404, inputStream); + HttpResponseException.Builder httpResponseExceptionBuilder = + new HttpResponseException.Builder(response); + httpResponseExceptionBuilder.setStatusCode(404); + httpResponseExceptionBuilder.setStatusMessage("NOT_FOUND"); + return new GoogleJsonResponseException(httpResponseExceptionBuilder, null); + } +} diff --git a/javatests/google/registry/keyring/kms/KmsKeyringTest.java b/javatests/google/registry/keyring/kms/KmsKeyringTest.java index b2d5a6239..77a260c93 100644 --- a/javatests/google/registry/keyring/kms/KmsKeyringTest.java +++ b/javatests/google/registry/keyring/kms/KmsKeyringTest.java @@ -17,20 +17,13 @@ package google.registry.keyring.kms; import static com.google.common.truth.Truth.assertThat; import static google.registry.testing.DatastoreHelper.persistResources; import static java.nio.charset.StandardCharsets.UTF_8; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyString; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import com.google.api.services.cloudkms.v1beta1.CloudKMS; -import com.google.api.services.cloudkms.v1beta1.model.DecryptRequest; -import com.google.api.services.cloudkms.v1beta1.model.DecryptResponse; import com.google.common.collect.ImmutableList; import google.registry.model.server.KmsSecret; import google.registry.model.server.KmsSecretRevision; import google.registry.model.server.KmsSecretRevision.Builder; import google.registry.testing.AppEngineRule; +import java.io.IOException; import org.bouncycastle.openpgp.PGPKeyPair; import org.bouncycastle.openpgp.PGPPrivateKey; import org.bouncycastle.openpgp.PGPPublicKey; @@ -38,307 +31,181 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.junit.runners.JUnit4; -@RunWith(MockitoJUnitRunner.class) +@RunWith(JUnit4.class) public class KmsKeyringTest { @Rule public final AppEngineRule appEngine = AppEngineRule.builder().withDatastore().build(); - @Mock private CloudKMS kms; - @Mock private CloudKMS.Projects kmsProjects; - @Mock private CloudKMS.Projects.Locations kmsLocations; - @Mock private CloudKMS.Projects.Locations.KeyRings kmsKeyRings; - @Mock private CloudKMS.Projects.Locations.KeyRings.CryptoKeys kmsCryptoKeys; - @Mock private CloudKMS.Projects.Locations.KeyRings.CryptoKeys.Decrypt kmsCryptoKeysDecrypt; - - @Captor private ArgumentCaptor cryptoKeyName; - @Captor private ArgumentCaptor decryptRequest; - private KmsKeyring keyring; @Before - public void setUp() throws Exception { - keyring = new KmsKeyring("foo", "bar", kms); - - when(kms.projects()).thenReturn(kmsProjects); - when(kmsProjects.locations()).thenReturn(kmsLocations); - when(kmsLocations.keyRings()).thenReturn(kmsKeyRings); - when(kmsKeyRings.cryptoKeys()).thenReturn(kmsCryptoKeys); - when(kmsCryptoKeys.decrypt(anyString(), any(DecryptRequest.class))) - .thenReturn(kmsCryptoKeysDecrypt); + public void setUp() { + keyring = new KmsKeyring(new FakeKmsConnection()); } @Test public void test_getRdeSigningKey() throws Exception { - persistSecret("rde-signing-private"); - persistSecret("rde-signing-public"); - when(kmsCryptoKeysDecrypt.execute()) - .thenReturn( - new DecryptResponse() - .encodePlaintext(KmsTestHelper.getPublicKeyring().getPublicKey().getEncoded())) - .thenReturn( - new DecryptResponse().encodePlaintext(KmsTestHelper.getPrivateKeyring().getEncoded())); + saveKeyPairSecret("rde-signing-public", "rde-signing-private"); PGPKeyPair rdeSigningKey = keyring.getRdeSigningKey(); - verify(kmsCryptoKeys, times(2)).decrypt(cryptoKeyName.capture(), decryptRequest.capture()); - assertThat(cryptoKeyName.getAllValues()) - .isEqualTo( - ImmutableList.of( - "projects/foo/locations/global/keyRings/bar/cryptoKeys/rde-signing-public", - "projects/foo/locations/global/keyRings/bar/cryptoKeys/rde-signing-private")); - assertThat(decryptRequest.getAllValues()) - .isEqualTo( - ImmutableList.of( - new DecryptRequest().setCiphertext(KmsTestHelper.DUMMY_ENCRYPTED_VALUE), - new DecryptRequest().setCiphertext(KmsTestHelper.DUMMY_ENCRYPTED_VALUE))); assertThat(rdeSigningKey.getKeyID()) .isEqualTo(KmsTestHelper.getPublicKeyring().getPublicKey().getKeyID()); } @Test public void test_getRdeStagingEncryptionKey() throws Exception { - persistSecret("rde-staging-public"); - when(kmsCryptoKeysDecrypt.execute()) - .thenReturn( - new DecryptResponse() - .encodePlaintext(KmsTestHelper.getPublicKeyring().getPublicKey().getEncoded())); + savePublicKeySecret("rde-staging-public"); PGPPublicKey rdeStagingEncryptionKey = keyring.getRdeStagingEncryptionKey(); - verify(kmsCryptoKeys).decrypt(cryptoKeyName.capture(), decryptRequest.capture()); - assertThat(cryptoKeyName.getValue()) - .isEqualTo("projects/foo/locations/global/keyRings/bar/cryptoKeys/rde-staging-public"); - assertThat(decryptRequest.getValue()) - .isEqualTo(new DecryptRequest().setCiphertext(KmsTestHelper.DUMMY_ENCRYPTED_VALUE)); assertThat(rdeStagingEncryptionKey.getFingerprint()) .isEqualTo(KmsTestHelper.getPublicKeyring().getPublicKey().getFingerprint()); } @Test public void test_getRdeStagingDecryptionKey() throws Exception { - persistSecret("rde-staging-private"); - when(kmsCryptoKeysDecrypt.execute()) - .thenReturn( - new DecryptResponse().encodePlaintext(KmsTestHelper.getPrivateKeyring().getEncoded())); + savePrivateKeySecret("rde-staging-private"); PGPPrivateKey rdeStagingDecryptionKey = keyring.getRdeStagingDecryptionKey(); - verify(kmsCryptoKeys).decrypt(cryptoKeyName.capture(), decryptRequest.capture()); - assertThat(cryptoKeyName.getValue()) - .isEqualTo("projects/foo/locations/global/keyRings/bar/cryptoKeys/rde-staging-private"); - assertThat(decryptRequest.getValue()) - .isEqualTo(new DecryptRequest().setCiphertext(KmsTestHelper.DUMMY_ENCRYPTED_VALUE)); assertThat(rdeStagingDecryptionKey.getKeyID()) .isEqualTo(KmsTestHelper.getPrivateKeyring().getSecretKey().getKeyID()); } @Test public void test_getRdeReceiverKey() throws Exception { - persistSecret("rde-receiver-public"); - when(kmsCryptoKeysDecrypt.execute()) - .thenReturn( - new DecryptResponse().encodePlaintext(KmsTestHelper.getPublicKeyring().getEncoded())); + savePublicKeySecret("rde-receiver-public"); PGPPublicKey rdeReceiverKey = keyring.getRdeReceiverKey(); - verify(kmsCryptoKeys).decrypt(cryptoKeyName.capture(), decryptRequest.capture()); - assertThat(cryptoKeyName.getValue()) - .isEqualTo("projects/foo/locations/global/keyRings/bar/cryptoKeys/rde-receiver-public"); - assertThat(decryptRequest.getValue()) - .isEqualTo(new DecryptRequest().setCiphertext(KmsTestHelper.DUMMY_ENCRYPTED_VALUE)); assertThat(rdeReceiverKey.getFingerprint()) .isEqualTo(KmsTestHelper.getPublicKeyring().getPublicKey().getFingerprint()); } @Test public void test_getBrdaSigningKey() throws Exception { - persistSecret("brda-signing-public"); - persistSecret("brda-signing-private"); - when(kmsCryptoKeysDecrypt.execute()) - .thenReturn( - new DecryptResponse() - .encodePlaintext(KmsTestHelper.getPublicKeyring().getPublicKey().getEncoded())) - .thenReturn( - new DecryptResponse().encodePlaintext(KmsTestHelper.getPrivateKeyring().getEncoded())); + saveKeyPairSecret("brda-signing-public", "brda-signing-private"); PGPKeyPair brdaSigningKey = keyring.getBrdaSigningKey(); - verify(kmsCryptoKeys, times(2)).decrypt(cryptoKeyName.capture(), decryptRequest.capture()); - assertThat(cryptoKeyName.getAllValues()) - .isEqualTo( - ImmutableList.of( - "projects/foo/locations/global/keyRings/bar/cryptoKeys/brda-signing-public", - "projects/foo/locations/global/keyRings/bar/cryptoKeys/brda-signing-private")); - assertThat(decryptRequest.getAllValues()) - .isEqualTo( - ImmutableList.of( - new DecryptRequest().setCiphertext(KmsTestHelper.DUMMY_ENCRYPTED_VALUE), - new DecryptRequest().setCiphertext(KmsTestHelper.DUMMY_ENCRYPTED_VALUE))); assertThat(brdaSigningKey.getKeyID()) .isEqualTo(KmsTestHelper.getPrivateKeyring().getPublicKey().getKeyID()); } @Test public void test_getBrdaReceiverKey() throws Exception { - persistSecret("brda-receiver-public"); - when(kmsCryptoKeysDecrypt.execute()) - .thenReturn( - new DecryptResponse() - .encodePlaintext(KmsTestHelper.getPublicKeyring().getPublicKey().getEncoded())); + savePublicKeySecret("brda-receiver-public"); PGPPublicKey brdaReceiverKey = keyring.getBrdaReceiverKey(); - verify(kmsCryptoKeys).decrypt(cryptoKeyName.capture(), decryptRequest.capture()); - assertThat(cryptoKeyName.getValue()) - .isEqualTo("projects/foo/locations/global/keyRings/bar/cryptoKeys/brda-receiver-public"); - assertThat(decryptRequest.getValue()) - .isEqualTo(new DecryptRequest().setCiphertext(KmsTestHelper.DUMMY_ENCRYPTED_VALUE)); assertThat(brdaReceiverKey.getFingerprint()) .isEqualTo(KmsTestHelper.getPublicKeyring().getPublicKey().getFingerprint()); } @Test public void test_getRdeSshClientPublicKey() throws Exception { - persistSecret("rde-ssh-client-public"); - when(kmsCryptoKeysDecrypt.execute()) - .thenReturn(new DecryptResponse().encodePlaintext(KmsTestHelper.DUMMY_KEY.getBytes(UTF_8))); + saveCleartextSecret("rde-ssh-client-public"); String rdeSshClientPublicKey = keyring.getRdeSshClientPublicKey(); - verify(kmsCryptoKeys).decrypt(cryptoKeyName.capture(), decryptRequest.capture()); - assertThat(cryptoKeyName.getValue()) - .isEqualTo("projects/foo/locations/global/keyRings/bar/cryptoKeys/rde-ssh-client-public"); - assertThat(decryptRequest.getValue()) - .isEqualTo(new DecryptRequest().setCiphertext(KmsTestHelper.DUMMY_ENCRYPTED_VALUE)); - assertThat(rdeSshClientPublicKey).isEqualTo(KmsTestHelper.DUMMY_KEY); + assertThat(rdeSshClientPublicKey).isEqualTo("rde-ssh-client-publicmoo"); } @Test public void test_getRdeSshClientPrivateKey() throws Exception { - persistSecret("rde-ssh-client-private"); - when(kmsCryptoKeysDecrypt.execute()) - .thenReturn(new DecryptResponse().encodePlaintext(KmsTestHelper.DUMMY_KEY.getBytes(UTF_8))); + saveCleartextSecret("rde-ssh-client-private"); String rdeSshClientPrivateKey = keyring.getRdeSshClientPrivateKey(); - verify(kmsCryptoKeys).decrypt(cryptoKeyName.capture(), decryptRequest.capture()); - assertThat(cryptoKeyName.getValue()) - .isEqualTo("projects/foo/locations/global/keyRings/bar/cryptoKeys/rde-ssh-client-private"); - assertThat(decryptRequest.getValue()) - .isEqualTo(new DecryptRequest().setCiphertext(KmsTestHelper.DUMMY_ENCRYPTED_VALUE)); - assertThat(rdeSshClientPrivateKey).isEqualTo(KmsTestHelper.DUMMY_KEY); + assertThat(rdeSshClientPrivateKey).isEqualTo("rde-ssh-client-privatemoo"); } @Test public void test_getIcannReportingPassword() throws Exception { - persistSecret("icann-reporting-password"); - when(kmsCryptoKeysDecrypt.execute()) - .thenReturn(new DecryptResponse().encodePlaintext("icann123".getBytes(UTF_8))); + saveCleartextSecret("icann-reporting-password"); String icannReportingPassword = keyring.getIcannReportingPassword(); - verify(kmsCryptoKeys).decrypt(cryptoKeyName.capture(), decryptRequest.capture()); - assertThat(cryptoKeyName.getValue()) - .isEqualTo( - "projects/foo/locations/global/keyRings/bar/cryptoKeys/icann-reporting-password"); - assertThat(decryptRequest.getValue()) - .isEqualTo(new DecryptRequest().setCiphertext(KmsTestHelper.DUMMY_ENCRYPTED_VALUE)); - assertThat(icannReportingPassword).isEqualTo("icann123"); + assertThat(icannReportingPassword).isEqualTo("icann-reporting-passwordmoo"); } @Test public void test_getMarksdbDnlLogin() throws Exception { - persistSecret("marksdb-dnl-login"); - when(kmsCryptoKeysDecrypt.execute()) - .thenReturn(new DecryptResponse().encodePlaintext(KmsTestHelper.DUMMY_KEY.getBytes(UTF_8))); + saveCleartextSecret("marksdb-dnl-login"); String marksdbDnlLogin = keyring.getMarksdbDnlLogin(); - verify(kmsCryptoKeys).decrypt(cryptoKeyName.capture(), decryptRequest.capture()); - assertThat(cryptoKeyName.getValue()) - .isEqualTo("projects/foo/locations/global/keyRings/bar/cryptoKeys/marksdb-dnl-login"); - assertThat(decryptRequest.getValue()) - .isEqualTo(new DecryptRequest().setCiphertext(KmsTestHelper.DUMMY_ENCRYPTED_VALUE)); - assertThat(marksdbDnlLogin).isEqualTo(KmsTestHelper.DUMMY_KEY); + assertThat(marksdbDnlLogin).isEqualTo("marksdb-dnl-loginmoo"); } @Test public void test_getMarksdbLordnPassword() throws Exception { - persistSecret("marksdb-lordn-password"); - when(kmsCryptoKeysDecrypt.execute()) - .thenReturn(new DecryptResponse().encodePlaintext(KmsTestHelper.DUMMY_KEY.getBytes(UTF_8))); + saveCleartextSecret("marksdb-lordn-password"); String marksdbLordnPassword = keyring.getMarksdbLordnPassword(); - verify(kmsCryptoKeys).decrypt(cryptoKeyName.capture(), decryptRequest.capture()); - assertThat(cryptoKeyName.getValue()) - .isEqualTo("projects/foo/locations/global/keyRings/bar/cryptoKeys/marksdb-lordn-password"); - assertThat(decryptRequest.getValue()) - .isEqualTo(new DecryptRequest().setCiphertext(KmsTestHelper.DUMMY_ENCRYPTED_VALUE)); - assertThat(marksdbLordnPassword).isEqualTo(KmsTestHelper.DUMMY_KEY); + assertThat(marksdbLordnPassword).isEqualTo("marksdb-lordn-passwordmoo"); } @Test public void test_getMarksdbSmdrlLogin() throws Exception { - persistSecret("marksdb-smdrl-login"); - when(kmsCryptoKeysDecrypt.execute()) - .thenReturn(new DecryptResponse().encodePlaintext(KmsTestHelper.DUMMY_KEY.getBytes(UTF_8))); + saveCleartextSecret("marksdb-smdrl-login"); String marksdbSmdrlLogin = keyring.getMarksdbSmdrlLogin(); - verify(kmsCryptoKeys).decrypt(cryptoKeyName.capture(), decryptRequest.capture()); - assertThat(cryptoKeyName.getValue()) - .isEqualTo("projects/foo/locations/global/keyRings/bar/cryptoKeys/marksdb-smdrl-login"); - assertThat(decryptRequest.getValue()) - .isEqualTo(new DecryptRequest().setCiphertext(KmsTestHelper.DUMMY_ENCRYPTED_VALUE)); - assertThat(marksdbSmdrlLogin).isEqualTo(KmsTestHelper.DUMMY_KEY); + assertThat(marksdbSmdrlLogin).isEqualTo("marksdb-smdrl-loginmoo"); + } @Test public void test_getJsonCredential() throws Exception { - persistSecret("json-credential"); - when(kmsCryptoKeysDecrypt.execute()) - .thenReturn(new DecryptResponse().encodePlaintext(KmsTestHelper.DUMMY_KEY.getBytes(UTF_8))); + saveCleartextSecret("json-credential"); String jsonCredential = keyring.getJsonCredential(); - verify(kmsCryptoKeys).decrypt(cryptoKeyName.capture(), decryptRequest.capture()); - assertThat(cryptoKeyName.getValue()) - .isEqualTo("projects/foo/locations/global/keyRings/bar/cryptoKeys/json-credential"); - assertThat(decryptRequest.getValue()) - .isEqualTo(new DecryptRequest().setCiphertext(KmsTestHelper.DUMMY_ENCRYPTED_VALUE)); - assertThat(jsonCredential).isEqualTo(KmsTestHelper.DUMMY_KEY); + assertThat(jsonCredential).isEqualTo("json-credentialmoo"); } @Test public void test_getBraintreePrivateKey() throws Exception { - persistSecret("braintree-private-key"); - when(kmsCryptoKeysDecrypt.execute()) - .thenReturn(new DecryptResponse().encodePlaintext(KmsTestHelper.DUMMY_KEY.getBytes(UTF_8))); + saveCleartextSecret("braintree-private-key"); String braintreePrivateKey = keyring.getBraintreePrivateKey(); - verify(kmsCryptoKeys).decrypt(cryptoKeyName.capture(), decryptRequest.capture()); - assertThat(cryptoKeyName.getValue()) - .isEqualTo("projects/foo/locations/global/keyRings/bar/cryptoKeys/braintree-private-key"); - assertThat(decryptRequest.getValue()) - .isEqualTo(new DecryptRequest().setCiphertext(KmsTestHelper.DUMMY_ENCRYPTED_VALUE)); - assertThat(braintreePrivateKey).isEqualTo(KmsTestHelper.DUMMY_KEY); + assertThat(braintreePrivateKey).isEqualTo("braintree-private-keymoo"); } - private static void persistSecret(String secretName) { + private static void persistSecret(String secretName, byte[] secretValue) throws IOException { + KmsConnection kmsConnection = new FakeKmsConnection(); + KmsSecretRevision secretRevision = new Builder() - .setEncryptedValue(KmsTestHelper.DUMMY_ENCRYPTED_VALUE) + .setEncryptedValue(kmsConnection.encrypt(secretName, secretValue).ciphertext()) .setKmsCryptoKeyVersionName(KmsTestHelper.DUMMY_CRYPTO_KEY_VERSION) .setParent(secretName) .build(); KmsSecret secret = KmsSecret.create(secretName, secretRevision); persistResources(ImmutableList.of(secretRevision, secret)); } + + private static void saveCleartextSecret(String secretName) throws Exception { + persistSecret(secretName, (secretName + "moo").getBytes(UTF_8)); + } + + private static void savePublicKeySecret(String publicKeyName) throws Exception { + persistSecret(publicKeyName, KmsTestHelper.getPublicKeyring().getPublicKey().getEncoded()); + } + + private static void savePrivateKeySecret(String privateKeyName) throws Exception { + persistSecret(privateKeyName, KmsTestHelper.getPrivateKeyring().getEncoded()); + } + + private static void saveKeyPairSecret(String publicKeyName, String privateKeyName) + throws Exception { + savePublicKeySecret(publicKeyName); + savePrivateKeySecret(privateKeyName); + } } diff --git a/javatests/google/registry/keyring/kms/KmsTestHelper.java b/javatests/google/registry/keyring/kms/KmsTestHelper.java index 5865fd84c..ed4918770 100644 --- a/javatests/google/registry/keyring/kms/KmsTestHelper.java +++ b/javatests/google/registry/keyring/kms/KmsTestHelper.java @@ -25,7 +25,6 @@ import org.bouncycastle.openpgp.bc.BcPGPSecretKeyRing; /** Stores dummy values for test use in {@link KmsUpdaterTest} and {@link KmsKeyringTest}. */ final class KmsTestHelper { - static final String DUMMY_KEY = "the quick brown fox"; static final String DUMMY_CRYPTO_KEY_VERSION = "cheeseburger"; static final String DUMMY_ENCRYPTED_VALUE = "meow"; diff --git a/javatests/google/registry/keyring/kms/KmsUpdaterTest.java b/javatests/google/registry/keyring/kms/KmsUpdaterTest.java index 9163b0d92..2226f8f5b 100644 --- a/javatests/google/registry/keyring/kms/KmsUpdaterTest.java +++ b/javatests/google/registry/keyring/kms/KmsUpdaterTest.java @@ -18,123 +18,28 @@ import static com.google.common.truth.Truth.assertThat; import static google.registry.model.common.EntityGroupRoot.getCrossTldKey; import static google.registry.model.ofy.ObjectifyService.ofy; import static java.nio.charset.StandardCharsets.UTF_8; -import static org.mockito.Matchers.anyString; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import com.google.api.client.googleapis.json.GoogleJsonResponseException; -import com.google.api.client.http.HttpResponse; -import com.google.api.client.http.HttpResponseException; -import com.google.api.services.cloudkms.v1beta1.CloudKMS; -import com.google.api.services.cloudkms.v1beta1.model.CryptoKey; -import com.google.api.services.cloudkms.v1beta1.model.CryptoKeyVersion; -import com.google.api.services.cloudkms.v1beta1.model.EncryptRequest; -import com.google.api.services.cloudkms.v1beta1.model.EncryptResponse; -import com.google.api.services.cloudkms.v1beta1.model.KeyRing; -import com.google.common.collect.ImmutableList; import com.googlecode.objectify.Key; import google.registry.model.server.KmsSecret; import google.registry.model.server.KmsSecretRevision; import google.registry.testing.AppEngineRule; -import java.io.ByteArrayInputStream; +import java.io.IOException; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.junit.runners.JUnit4; -@RunWith(MockitoJUnitRunner.class) +@RunWith(JUnit4.class) public class KmsUpdaterTest { @Rule public final AppEngineRule appEngine = AppEngineRule.builder().withDatastore().build(); - @Mock private CloudKMS kms; - @Mock private CloudKMS.Projects kmsProjects; - @Mock private CloudKMS.Projects.Locations kmsLocations; - @Mock private CloudKMS.Projects.Locations.KeyRings kmsKeyRings; - @Mock private CloudKMS.Projects.Locations.KeyRings.Get kmsKeyRingsGet; - @Mock private CloudKMS.Projects.Locations.KeyRings.Create kmsKeyRingsCreate; - @Mock private CloudKMS.Projects.Locations.KeyRings.CryptoKeys kmsCryptoKeys; - @Mock private CloudKMS.Projects.Locations.KeyRings.CryptoKeys.Get kmsCryptoKeysGet; - @Mock private CloudKMS.Projects.Locations.KeyRings.CryptoKeys.Create kmsCryptoKeysCreate; - - @Mock - private CloudKMS.Projects.Locations.KeyRings.CryptoKeys.CryptoKeyVersions kmsCryptoKeyVersions; - - @Mock - private CloudKMS.Projects.Locations.KeyRings.CryptoKeys.CryptoKeyVersions.Create - kmsCryptoKeyVersionsCreate; - - @Mock private CloudKMS.Projects.Locations.KeyRings.CryptoKeys.Encrypt kmsCryptoKeysEncrypt; - - @Captor private ArgumentCaptor keyRing; - @Captor private ArgumentCaptor cryptoKey; - @Captor private ArgumentCaptor cryptoKeyVersion; - @Captor private ArgumentCaptor keyRingName; - @Captor private ArgumentCaptor cryptoKeyName; - @Captor private ArgumentCaptor cryptoKeyVersionName; - @Captor private ArgumentCaptor encryptRequest; - private KmsUpdater updater; @Before - public void setUp() throws Exception { - when(kms.projects()).thenReturn(kmsProjects); - when(kmsProjects.locations()).thenReturn(kmsLocations); - when(kmsLocations.keyRings()).thenReturn(kmsKeyRings); - when(kmsKeyRings.get(anyString())).thenReturn(kmsKeyRingsGet); - when(kmsKeyRings.create(anyString(), any(KeyRing.class))).thenReturn(kmsKeyRingsCreate); - when(kmsKeyRings.cryptoKeys()).thenReturn(kmsCryptoKeys); - when(kmsCryptoKeys.get(anyString())).thenReturn(kmsCryptoKeysGet); - when(kmsCryptoKeys.create(anyString(), any(CryptoKey.class))).thenReturn(kmsCryptoKeysCreate); - when(kmsCryptoKeys.cryptoKeyVersions()).thenReturn(kmsCryptoKeyVersions); - when(kmsCryptoKeyVersions.create(anyString(), any(CryptoKeyVersion.class))) - .thenReturn(kmsCryptoKeyVersionsCreate); - when(kmsCryptoKeyVersionsCreate.execute()) - .thenReturn(new CryptoKeyVersion().setName(KmsTestHelper.DUMMY_CRYPTO_KEY_VERSION)); - when(kmsCryptoKeys.encrypt(anyString(), any(EncryptRequest.class))) - .thenReturn(kmsCryptoKeysEncrypt); - when(kmsCryptoKeysEncrypt.execute()) - .thenReturn( - new EncryptResponse() - .setName(KmsTestHelper.DUMMY_CRYPTO_KEY_VERSION) - .setCiphertext(KmsTestHelper.DUMMY_ENCRYPTED_VALUE)); - - updater = new KmsUpdater("foo", "bar", kms); - } - - @Test - public void test_close_createsNewKeyRing_ifNotFound() throws Exception { - when(kmsKeyRingsGet.execute()).thenThrow(createNotFoundException()); - - updater.setBraintreePrivateKey(KmsTestHelper.DUMMY_KEY); - updater.update(); - - verify(kmsKeyRings).create(keyRingName.capture(), keyRing.capture()); - assertThat(keyRingName.getValue()).isEqualTo("global"); - assertThat(keyRing.getValue()) - .isEqualTo(new KeyRing().setName("projects/foo/locations/global/keyRings/bar")); - verify(kmsKeyRingsCreate).execute(); - } - - @Test - public void test_close_createsNewCryptoKey_ifNotFound() throws Exception { - when(kmsCryptoKeysGet.execute()).thenThrow(createNotFoundException()); - - updater.setBraintreePrivateKey(KmsTestHelper.DUMMY_KEY); - updater.update(); - - verify(kmsCryptoKeys).create(cryptoKeyName.capture(), cryptoKey.capture()); - assertThat(cryptoKeyName.getValue()) - .isEqualTo("projects/foo/locations/global/keyRings/bar/cryptoKeys/braintree-private-key"); - assertThat(cryptoKey.getValue()) - .isEqualTo(new CryptoKey().setName("braintree-private-key").setPurpose("ENCRYPT_DECRYPT")); - verify(kmsCryptoKeysCreate).execute(); + public void setUp() { + updater = new KmsUpdater(new FakeKmsConnection()); } @Test @@ -142,336 +47,159 @@ public class KmsUpdaterTest { updater .setBraintreePrivateKey("value1") .setIcannReportingPassword("value2") - .setJsonCredential("value3"); - updater.update(); + .setJsonCredential("value3") + .update(); - verify(kmsCryptoKeys, times(3)).get(cryptoKeyName.capture()); - assertThat(cryptoKeyName.getAllValues()) - .isEqualTo( - ImmutableList.of( - "projects/foo/locations/" + "global/keyRings/bar/cryptoKeys/braintree-private-key", - "projects/foo/locations/" - + "global/keyRings/bar/cryptoKeys/icann-reporting-password", - "projects/foo/locations/" + "global/keyRings/bar/cryptoKeys/json-credential")); - - verify(kmsCryptoKeyVersions, times(3)) - .create(cryptoKeyVersionName.capture(), cryptoKeyVersion.capture()); - assertThat(cryptoKeyVersionName.getAllValues()) - .isEqualTo( - ImmutableList.of( - "projects/foo/locations/" - + "global/keyRings/bar/cryptoKeys/braintree-private-key/cryptoKeyVersions", - "projects/foo/locations/" - + "global/keyRings/bar/cryptoKeys/icann-reporting-password/cryptoKeyVersions", - "projects/foo/locations/" - + "global/keyRings/bar/cryptoKeys/json-credential/cryptoKeyVersions")); - - verify(kmsCryptoKeys, times(3)).encrypt(cryptoKeyName.capture(), encryptRequest.capture()); - assertThat(cryptoKeyName.getValue()).isEqualTo(KmsTestHelper.DUMMY_CRYPTO_KEY_VERSION); - assertThat(encryptRequest.getAllValues()) - .isEqualTo( - ImmutableList.of( - new EncryptRequest().encodePlaintext("value1".getBytes(UTF_8)), - new EncryptRequest().encodePlaintext("value2".getBytes(UTF_8)), - new EncryptRequest().encodePlaintext("value3".getBytes(UTF_8)))); - - KmsSecret firstSecret = loadSecret("braintree-private-key"); - assertThat(firstSecret).isNotNull(); - assertThat(firstSecret.getLatestRevision()).isNotNull(); - - KmsSecret secondSecret = loadSecret("icann-reporting-password"); - assertThat(secondSecret).isNotNull(); - assertThat(secondSecret.getLatestRevision()).isNotNull(); - - KmsSecret thirdSecret = loadSecret("icann-reporting-password"); - assertThat(thirdSecret).isNotNull(); - assertThat(thirdSecret.getLatestRevision()).isNotNull(); + verifySecretAndSecretRevisionWritten( + "braintree-private-key", "braintree-private-key/foo", getCiphertext("value1")); + verifySecretAndSecretRevisionWritten( + "icann-reporting-password", "icann-reporting-password/foo", getCiphertext("value2")); + verifySecretAndSecretRevisionWritten( + "json-credential", "json-credential/foo", getCiphertext("value3")); } @Test public void test_setBraintreePrivateKey() throws Exception { - updater.setBraintreePrivateKey(KmsTestHelper.DUMMY_KEY); - updater.update(); + updater.setBraintreePrivateKey("value1").update(); - verifyKmsApiCallsAndDatastoreWrites( - "braintree-private-key", - KmsTestHelper.DUMMY_KEY.getBytes(UTF_8), - "projects/foo/locations/global/keyRings/bar", - "projects/foo/locations/global/keyRings/bar/cryptoKeys/braintree-private-key", - "projects/foo/locations/" - + "global/keyRings/bar/cryptoKeys/braintree-private-key/cryptoKeyVersions"); + verifySecretAndSecretRevisionWritten( + "braintree-private-key", "braintree-private-key/foo", getCiphertext("value1")); } @Test public void test_setBrdaReceiverKey() throws Exception { - updater.setBrdaReceiverPublicKey(KmsTestHelper.getPublicKeyring().getPublicKey()); - updater.update(); + updater.setBrdaReceiverPublicKey(KmsTestHelper.getPublicKeyring().getPublicKey()).update(); - verifyKmsApiCallsAndDatastoreWrites( + verifySecretAndSecretRevisionWritten( "brda-receiver-public", - KmsTestHelper.getPublicKeyring().getPublicKey().getEncoded(), - "projects/foo/locations/global/keyRings/bar", - "projects/foo/locations/global/keyRings/bar/cryptoKeys/brda-receiver-public", - "projects/foo/locations/" - + "global/keyRings/bar/cryptoKeys/brda-receiver-public/cryptoKeyVersions"); + "brda-receiver-public/foo", + getCiphertext(KmsTestHelper.getPublicKeyring().getPublicKey().getEncoded())); } @Test public void test_setBrdaSigningKey() throws Exception { - updater.setBrdaSigningKey(KmsTestHelper.getPrivateKeyring()); - updater.update(); + updater.setBrdaSigningKey(KmsTestHelper.getPrivateKeyring()).update(); - verifyKmsApiCallsAndDatastoreWrites( + verifySecretAndSecretRevisionWritten( "brda-signing-private", - KmsTestHelper.getPrivateKeyring().getEncoded(), - "projects/foo/locations/global/keyRings/bar", - "projects/foo/locations/global/keyRings/bar/cryptoKeys/brda-signing-private", - "projects/foo/locations/" - + "global/keyRings/bar/cryptoKeys/brda-signing-private/cryptoKeyVersions", + "brda-signing-private/foo", + getCiphertext(KmsTestHelper.getPrivateKeyring().getEncoded())); + verifySecretAndSecretRevisionWritten( "brda-signing-public", - KmsTestHelper.getPublicKeyring().getPublicKey().getEncoded(), - "projects/foo/locations/global/keyRings/bar/cryptoKeys/brda-signing-public", - "projects/foo/locations/" - + "global/keyRings/bar/cryptoKeys/brda-signing-public/cryptoKeyVersions"); + "brda-signing-public/foo", + getCiphertext(KmsTestHelper.getPrivateKeyring().getPublicKey().getEncoded())); } @Test public void test_setIcannReportingPassword() throws Exception { - updater.setIcannReportingPassword(KmsTestHelper.DUMMY_KEY); - updater.update(); + updater.setIcannReportingPassword("value1").update(); - verifyKmsApiCallsAndDatastoreWrites( - "icann-reporting-password", - KmsTestHelper.DUMMY_KEY.getBytes(UTF_8), - "projects/foo/locations/global/keyRings/bar", - "projects/foo/locations/global/keyRings/bar/cryptoKeys/icann-reporting-password", - "projects/foo/locations/" - + "global/keyRings/bar/cryptoKeys/icann-reporting-password/cryptoKeyVersions"); + verifySecretAndSecretRevisionWritten( + "icann-reporting-password", "icann-reporting-password/foo", getCiphertext("value1")); } @Test public void test_setJsonCredential() throws Exception { - updater.setJsonCredential(KmsTestHelper.DUMMY_KEY); - updater.update(); + updater.setJsonCredential("value1").update(); - verifyKmsApiCallsAndDatastoreWrites( - "json-credential", - KmsTestHelper.DUMMY_KEY.getBytes(UTF_8), - "projects/foo/locations/global/keyRings/bar", - "projects/foo/locations/global/keyRings/bar/cryptoKeys/json-credential", - "projects/foo/locations/" - + "global/keyRings/bar/cryptoKeys/json-credential/cryptoKeyVersions"); + verifySecretAndSecretRevisionWritten( + "json-credential", "json-credential/foo", getCiphertext("value1")); } @Test public void test_setMarksdbDnlLogin() throws Exception { - updater.setMarksdbDnlLogin(KmsTestHelper.DUMMY_KEY); - updater.update(); + updater.setMarksdbDnlLogin("value1").update(); - verifyKmsApiCallsAndDatastoreWrites( - "marksdb-dnl-login", - KmsTestHelper.DUMMY_KEY.getBytes(UTF_8), - "projects/foo/locations/global/keyRings/bar", - "projects/foo/locations/global/keyRings/bar/cryptoKeys/marksdb-dnl-login", - "projects/foo/locations/" - + "global/keyRings/bar/cryptoKeys/marksdb-dnl-login/cryptoKeyVersions"); + verifySecretAndSecretRevisionWritten( + "marksdb-dnl-login", "marksdb-dnl-login/foo", getCiphertext("value1")); } @Test public void test_setMarksdbLordnPassword() throws Exception { - updater.setMarksdbLordnPassword(KmsTestHelper.DUMMY_KEY); - updater.update(); + updater.setMarksdbLordnPassword("value1").update(); - verifyKmsApiCallsAndDatastoreWrites( - "marksdb-lordn-password", - KmsTestHelper.DUMMY_KEY.getBytes(UTF_8), - "projects/foo/locations/global/keyRings/bar", - "projects/foo/locations/global/keyRings/bar/cryptoKeys/marksdb-lordn-password", - "projects/foo/locations/" - + "global/keyRings/bar/cryptoKeys/marksdb-lordn-password/cryptoKeyVersions"); + verifySecretAndSecretRevisionWritten( + "marksdb-lordn-password", "marksdb-lordn-password/foo", getCiphertext("value1")); } @Test public void test_setMarksdbSmdrlLogin() throws Exception { - updater.setMarksdbSmdrlLogin(KmsTestHelper.DUMMY_KEY); - updater.update(); + updater.setMarksdbSmdrlLogin("value1").update(); - verifyKmsApiCallsAndDatastoreWrites( - "marksdb-smdrl-login", - KmsTestHelper.DUMMY_KEY.getBytes(UTF_8), - "projects/foo/locations/global/keyRings/bar", - "projects/foo/locations/global/keyRings/bar/cryptoKeys/marksdb-smdrl-login", - "projects/foo/locations/" - + "global/keyRings/bar/cryptoKeys/marksdb-smdrl-login/cryptoKeyVersions"); + verifySecretAndSecretRevisionWritten( + "marksdb-smdrl-login", "marksdb-smdrl-login/foo", getCiphertext("value1")); } @Test public void test_setRdeReceiverKey() throws Exception { - updater.setRdeReceiverPublicKey(KmsTestHelper.getPublicKeyring().getPublicKey()); - updater.update(); + updater.setRdeReceiverPublicKey(KmsTestHelper.getPublicKeyring().getPublicKey()).update(); - verifyKmsApiCallsAndDatastoreWrites( + verifySecretAndSecretRevisionWritten( "rde-receiver-public", - KmsTestHelper.getPublicKeyring().getPublicKey().getEncoded(), - "projects/foo/locations/global/keyRings/bar", - "projects/foo/locations/global/keyRings/bar/cryptoKeys/rde-receiver-public", - "projects/foo/locations/" - + "global/keyRings/bar/cryptoKeys/rde-receiver-public/cryptoKeyVersions"); + "rde-receiver-public/foo", + getCiphertext(KmsTestHelper.getPublicKeyring().getPublicKey().getEncoded())); } @Test public void test_setRdeSigningKey() throws Exception { - updater.setRdeSigningKey(KmsTestHelper.getPrivateKeyring()); - updater.update(); + updater.setRdeSigningKey(KmsTestHelper.getPrivateKeyring()).update(); - verifyKmsApiCallsAndDatastoreWrites( + verifySecretAndSecretRevisionWritten( "rde-signing-private", - KmsTestHelper.getPrivateKeyring().getEncoded(), - "projects/foo/locations/global/keyRings/bar", - "projects/foo/locations/global/keyRings/bar/cryptoKeys/rde-signing-private", - "projects/foo/locations/" - + "global/keyRings/bar/cryptoKeys/rde-signing-private/cryptoKeyVersions", + "rde-signing-private/foo", + getCiphertext(KmsTestHelper.getPrivateKeyring().getEncoded())); + verifySecretAndSecretRevisionWritten( "rde-signing-public", - KmsTestHelper.getPublicKeyring().getPublicKey().getEncoded(), - "projects/foo/locations/global/keyRings/bar/cryptoKeys/rde-signing-public", - "projects/foo/locations/" - + "global/keyRings/bar/cryptoKeys/rde-signing-public/cryptoKeyVersions"); + "rde-signing-public/foo", + getCiphertext(KmsTestHelper.getPrivateKeyring().getPublicKey().getEncoded())); } @Test public void test_setRdeSshClientPrivateKey() throws Exception { - updater.setRdeSshClientPrivateKey(KmsTestHelper.DUMMY_KEY); - updater.update(); + updater.setRdeSshClientPrivateKey("value1").update(); - verifyKmsApiCallsAndDatastoreWrites( - "rde-ssh-client-private", - KmsTestHelper.DUMMY_KEY.getBytes(UTF_8), - "projects/foo/locations/global/keyRings/bar", - "projects/foo/locations/global/keyRings/bar/cryptoKeys/rde-ssh-client-private", - "projects/foo/locations/" - + "global/keyRings/bar/cryptoKeys/rde-ssh-client-private/cryptoKeyVersions"); + verifySecretAndSecretRevisionWritten( + "rde-ssh-client-private", "rde-ssh-client-private/foo", getCiphertext("value1")); } @Test public void test_setRdeSshClientPublicKey() throws Exception { - updater.setRdeSshClientPublicKey(KmsTestHelper.DUMMY_KEY); - updater.update(); + updater.setRdeSshClientPublicKey("value1").update(); - verifyKmsApiCallsAndDatastoreWrites( - "rde-ssh-client-public", - KmsTestHelper.DUMMY_KEY.getBytes(UTF_8), - "projects/foo/locations/global/keyRings/bar", - "projects/foo/locations/global/keyRings/bar/cryptoKeys/rde-ssh-client-public", - "projects/foo/locations/" - + "global/keyRings/bar/cryptoKeys/rde-ssh-client-public/cryptoKeyVersions"); + verifySecretAndSecretRevisionWritten( + "rde-ssh-client-public", "rde-ssh-client-public/foo", getCiphertext("value1")); } @Test public void test_setRdeStagingKey() throws Exception { - updater.setRdeStagingKey(KmsTestHelper.getPrivateKeyring()); - updater.update(); + updater.setRdeStagingKey(KmsTestHelper.getPrivateKeyring()).update(); - verifyKmsApiCallsAndDatastoreWrites( + verifySecretAndSecretRevisionWritten( "rde-staging-private", - KmsTestHelper.getPrivateKeyring().getEncoded(), - "projects/foo/locations/global/keyRings/bar", - "projects/foo/locations/global/keyRings/bar/cryptoKeys/rde-staging-private", - "projects/foo/locations/" - + "global/keyRings/bar/cryptoKeys/rde-staging-private/cryptoKeyVersions", + "rde-staging-private/foo", + getCiphertext(KmsTestHelper.getPrivateKeyring().getEncoded())); + verifySecretAndSecretRevisionWritten( "rde-staging-public", - KmsTestHelper.getPublicKeyring().getPublicKey().getEncoded(), - "projects/foo/locations/global/keyRings/bar/cryptoKeys/rde-staging-public", - "projects/foo/locations/" - + "global/keyRings/bar/cryptoKeys/rde-staging-public/cryptoKeyVersions"); + "rde-staging-public/foo", + getCiphertext(KmsTestHelper.getPrivateKeyring().getPublicKey().getEncoded())); } - private void verifyKmsApiCallsAndDatastoreWrites( - String secretName, - byte[] goldenValue, - String goldenCryptoKeyRingName, - String goldenCryptoKeyName, - String goldenCryptoKeyVersionName) - throws Exception { - verify(kmsKeyRings).get(keyRingName.capture()); - assertThat(keyRingName.getValue()).isEqualTo(goldenCryptoKeyRingName); - verify(kmsCryptoKeys).get(cryptoKeyName.capture()); - assertThat(cryptoKeyName.getValue()).isEqualTo(goldenCryptoKeyName); - - verify(kmsCryptoKeyVersions).create(cryptoKeyVersionName.capture(), cryptoKeyVersion.capture()); - assertThat(cryptoKeyVersionName.getValue()).isEqualTo(goldenCryptoKeyVersionName); - - verify(kmsCryptoKeys).encrypt(cryptoKeyName.capture(), encryptRequest.capture()); - assertThat(cryptoKeyName.getValue()).isEqualTo(KmsTestHelper.DUMMY_CRYPTO_KEY_VERSION); - assertThat(encryptRequest.getValue()) - .isEqualTo(new EncryptRequest().encodePlaintext(goldenValue)); - - KmsSecret secret = loadSecret(secretName); + private static void verifySecretAndSecretRevisionWritten( + String secretName, String expectedCryptoKeyVersionName, String expectedEncryptedValue) { + KmsSecret secret = + ofy().load().key(Key.create(getCrossTldKey(), KmsSecret.class, secretName)).now(); + assertThat(secret).isNotNull(); KmsSecretRevision secretRevision = ofy().load().key(secret.getLatestRevision()).now(); - assertThat(secretRevision.getKmsCryptoKeyVersionName()) - .isEqualTo(KmsTestHelper.DUMMY_CRYPTO_KEY_VERSION); - assertThat(secretRevision.getEncryptedValue()).isEqualTo(KmsTestHelper.DUMMY_ENCRYPTED_VALUE); + assertThat(secretRevision.getKmsCryptoKeyVersionName()).isEqualTo(expectedCryptoKeyVersionName); + assertThat(secretRevision.getEncryptedValue()).isEqualTo(expectedEncryptedValue); } - /** Variant of {@code verifyKmsApiCallsAndDatastoreWrites} for key pairs. */ - private void verifyKmsApiCallsAndDatastoreWrites( - String firstSecretName, - byte[] firstGoldenValue, - String goldenCryptoKeyRingName, - String firstGoldenCryptoKeyName, - String firstGoldenCryptoKeyVersionName, - String secondSecretName, - byte[] secondGoldenValue, - String secondGoldenCryptoKeyName, - String secondGoldenCryptoKeyVersionName) - throws Exception { - verify(kmsKeyRings, times(1)).get(keyRingName.capture()); - assertThat(keyRingName.getValue()).isEqualTo(goldenCryptoKeyRingName); - - verify(kmsCryptoKeys, times(2)).get(cryptoKeyName.capture()); - assertThat(cryptoKeyName.getAllValues()) - .isEqualTo(ImmutableList.of(firstGoldenCryptoKeyName, secondGoldenCryptoKeyName)); - - verify(kmsCryptoKeyVersions, times(2)) - .create(cryptoKeyVersionName.capture(), cryptoKeyVersion.capture()); - assertThat(cryptoKeyVersionName.getAllValues()) - .isEqualTo( - ImmutableList.of(firstGoldenCryptoKeyVersionName, secondGoldenCryptoKeyVersionName)); - - verify(kmsCryptoKeys, times(2)).encrypt(cryptoKeyName.capture(), encryptRequest.capture()); - assertThat(cryptoKeyName.getValue()).isEqualTo(KmsTestHelper.DUMMY_CRYPTO_KEY_VERSION); - assertThat(encryptRequest.getAllValues()) - .isEqualTo( - ImmutableList.of( - new EncryptRequest().encodePlaintext(firstGoldenValue), - new EncryptRequest().encodePlaintext(secondGoldenValue))); - - KmsSecret secret = loadSecret(firstSecretName); - KmsSecretRevision secretRevision = ofy().load().key(secret.getLatestRevision()).now(); - assertThat(secretRevision.getKmsCryptoKeyVersionName()) - .isEqualTo(KmsTestHelper.DUMMY_CRYPTO_KEY_VERSION); - assertThat(secretRevision.getEncryptedValue()).isEqualTo(KmsTestHelper.DUMMY_ENCRYPTED_VALUE); - - KmsSecret secondSecret = loadSecret(secondSecretName); - KmsSecretRevision secondSecretRevision = - ofy().load().key(secondSecret.getLatestRevision()).now(); - assertThat(secondSecretRevision.getKmsCryptoKeyVersionName()) - .isEqualTo(KmsTestHelper.DUMMY_CRYPTO_KEY_VERSION); - assertThat(secondSecretRevision.getEncryptedValue()) - .isEqualTo(KmsTestHelper.DUMMY_ENCRYPTED_VALUE); + private static String getCiphertext(byte[] plaintext) throws IOException { + return new FakeKmsConnection().encrypt("blah", plaintext).ciphertext(); } - private static GoogleJsonResponseException createNotFoundException() throws Exception { - ByteArrayInputStream inputStream = new ByteArrayInputStream("".getBytes(UTF_8)); - HttpResponse response = GoogleJsonResponseExceptionHelper.createHttpResponse(404, inputStream); - HttpResponseException.Builder httpResponseExceptionBuilder = - new HttpResponseException.Builder(response); - httpResponseExceptionBuilder.setStatusCode(404); - httpResponseExceptionBuilder.setStatusMessage("NOT_FOUND"); - return new GoogleJsonResponseException(httpResponseExceptionBuilder, null); - } - - private static KmsSecret loadSecret(String secret) { - return ofy().load().key(Key.create(getCrossTldKey(), KmsSecret.class, secret)).now(); + private static String getCiphertext(String plaintext) throws IOException { + return getCiphertext(plaintext.getBytes(UTF_8)); } }