From 886aa62d468e9eee499401817c10b953b7d94fc0 Mon Sep 17 00:00:00 2001 From: jianglai Date: Mon, 26 Nov 2018 10:35:15 -0800 Subject: [PATCH] Expose encrypted data from the keyring This makes it possible to request the encrypted data directly in application code. It will be used to download service account credential during "nomulus login". ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=222847905 --- .../registry/keyring/api/InMemoryKeyring.java | 6 +++++ .../registry/keyring/api/KeyModule.java | 8 +++++++ java/google/registry/keyring/api/Keyring.java | 6 +++++ .../registry/keyring/kms/KmsKeyring.java | 24 +++++++++++++++---- .../registry/keyring/kms/KmsKeyringTest.java | 16 +++++++++++++ .../registry/testing/FakeKeyringModule.java | 6 +++++ 6 files changed, 61 insertions(+), 5 deletions(-) diff --git a/java/google/registry/keyring/api/InMemoryKeyring.java b/java/google/registry/keyring/api/InMemoryKeyring.java index d2bcf8775..30509ab88 100644 --- a/java/google/registry/keyring/api/InMemoryKeyring.java +++ b/java/google/registry/keyring/api/InMemoryKeyring.java @@ -149,6 +149,12 @@ public final class InMemoryKeyring implements Keyring { return jsonCredential; } + @Override + public String getEncryptedData(String keyName) { + throw new RuntimeException( + "In-memory keyring does not support the retrieval of encrypted data."); + } + /** Does nothing. */ @Override public void close() {} diff --git a/java/google/registry/keyring/api/KeyModule.java b/java/google/registry/keyring/api/KeyModule.java index d4282e28d..81ee1bd93 100644 --- a/java/google/registry/keyring/api/KeyModule.java +++ b/java/google/registry/keyring/api/KeyModule.java @@ -20,6 +20,8 @@ import dagger.Module; import dagger.Provides; import java.lang.annotation.Documented; import java.util.Optional; +import java.util.function.Function; +import javax.inject.Named; import javax.inject.Qualifier; import org.bouncycastle.openpgp.PGPKeyPair; import org.bouncycastle.openpgp.PGPPrivateKey; @@ -126,4 +128,10 @@ public final class KeyModule { static String provideJsonCredential(Keyring keyring) { return keyring.getJsonCredential(); } + + @Provides + @Named("encryptedDataRetriever") + static Function provideEncryptedDataRetriever(Keyring keyring) { + return keyring::getEncryptedData; + } } diff --git a/java/google/registry/keyring/api/Keyring.java b/java/google/registry/keyring/api/Keyring.java index e600ae529..74ee8bead 100644 --- a/java/google/registry/keyring/api/Keyring.java +++ b/java/google/registry/keyring/api/Keyring.java @@ -156,6 +156,12 @@ public interface Keyring extends AutoCloseable { */ String getJsonCredential(); + /** + * Returns the encrypted data for the given key name. Only use this method when decryption is not + * required. + */ + String getEncryptedData(String keyName); + // Don't throw so try-with-resources works better. @Override void close(); diff --git a/java/google/registry/keyring/kms/KmsKeyring.java b/java/google/registry/keyring/kms/KmsKeyring.java index 262145f91..75a2f97db 100644 --- a/java/google/registry/keyring/kms/KmsKeyring.java +++ b/java/google/registry/keyring/kms/KmsKeyring.java @@ -155,10 +155,27 @@ public class KmsKeyring implements Keyring { return getString(StringKeyLabel.JSON_CREDENTIAL_STRING); } + @Override + public String getEncryptedData(String keyName) { + KmsSecret secret = getSecret(keyName); + return ofy().load().key(secret.getLatestRevision()).now().getEncryptedValue(); + } + + private String getEncryptedData(KmsSecret secret) { + return ofy().load().key(secret.getLatestRevision()).now().getEncryptedValue(); + } + /** No persistent resources are maintained for this Keyring implementation. */ @Override public void close() {} + private KmsSecret getSecret(String keyName) { + KmsSecret secret = + ofy().load().key(Key.create(getCrossTldKey(), KmsSecret.class, keyName)).now(); + checkState(secret != null, "Requested secret '%s' does not exist.", keyName); + return secret; + } + private String getString(StringKeyLabel keyLabel) { return KeySerializer.deserializeString(getDecryptedData(keyLabel.getLabel())); } @@ -185,11 +202,8 @@ public class KmsKeyring implements Keyring { } private byte[] getDecryptedData(String keyName) { - KmsSecret secret = - ofy().load().key(Key.create(getCrossTldKey(), KmsSecret.class, keyName)).now(); - checkState(secret != null, "Requested secret '%s' does not exist.", keyName); - String encryptedData = ofy().load().key(secret.getLatestRevision()).now().getEncryptedValue(); - + KmsSecret secret = getSecret(keyName); + String encryptedData = getEncryptedData(secret); try { return kmsConnection.decrypt(secret.getName(), encryptedData); } catch (Exception e) { diff --git a/javatests/google/registry/keyring/kms/KmsKeyringTest.java b/javatests/google/registry/keyring/kms/KmsKeyringTest.java index 9bf52aca5..153bfaed9 100644 --- a/javatests/google/registry/keyring/kms/KmsKeyringTest.java +++ b/javatests/google/registry/keyring/kms/KmsKeyringTest.java @@ -16,17 +16,21 @@ 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.US_ASCII; import com.google.common.collect.ImmutableList; +import com.google.common.io.BaseEncoding; import google.registry.keyring.api.KeySerializer; 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 google.registry.testing.BouncyCastleProviderRule; +import java.io.UnsupportedEncodingException; import org.bouncycastle.openpgp.PGPKeyPair; import org.bouncycastle.openpgp.PGPPrivateKey; import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.util.Arrays; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -174,6 +178,18 @@ public class KmsKeyringTest { assertThat(jsonCredential).isEqualTo("json-credential-stringmoo"); } + @Test + public void test_getEncryptedJsonCredential() throws UnsupportedEncodingException { + saveCleartextSecret("json-credential-string"); + + String encryptedJsonCredential = keyring.getEncryptedData("json-credential-string"); + + assertThat( + new String( + Arrays.reverse(BaseEncoding.base64().decode(encryptedJsonCredential)), US_ASCII)) + .isEqualTo("json-credential-stringmoo"); + } + private static void persistSecret(String secretName, byte[] secretValue) { KmsConnection kmsConnection = new FakeKmsConnection(); diff --git a/javatests/google/registry/testing/FakeKeyringModule.java b/javatests/google/registry/testing/FakeKeyringModule.java index f0328e6b6..afb6887d4 100644 --- a/javatests/google/registry/testing/FakeKeyringModule.java +++ b/javatests/google/registry/testing/FakeKeyringModule.java @@ -150,6 +150,12 @@ public final class FakeKeyringModule { return rdeReceiverKey; } + @Override + public String getEncryptedData(String keyName) { + throw new RuntimeException( + "Fake keyring does not support the retrieval of encrypted data."); + } + @Override public void close() {} };