diff --git a/java/google/registry/keyring/api/InMemoryKeyring.java b/java/google/registry/keyring/api/InMemoryKeyring.java index 30509ab88..e81480ff5 100644 --- a/java/google/registry/keyring/api/InMemoryKeyring.java +++ b/java/google/registry/keyring/api/InMemoryKeyring.java @@ -155,6 +155,11 @@ public final class InMemoryKeyring implements Keyring { "In-memory keyring does not support the retrieval of encrypted data."); } + @Override + public byte[] getDecryptedData(String keyName, String encryptedData) { + throw new RuntimeException("In-memory keyring does not support decrypting of supplied 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 81ee1bd93..77016aec1 100644 --- a/java/google/registry/keyring/api/KeyModule.java +++ b/java/google/registry/keyring/api/KeyModule.java @@ -20,6 +20,7 @@ import dagger.Module; import dagger.Provides; import java.lang.annotation.Documented; import java.util.Optional; +import java.util.function.BiFunction; import java.util.function.Function; import javax.inject.Named; import javax.inject.Qualifier; @@ -134,4 +135,10 @@ public final class KeyModule { static Function provideEncryptedDataRetriever(Keyring keyring) { return keyring::getEncryptedData; } + + @Provides + @Named("keyringDecrypter") + static BiFunction provideKeyringDecrypter(Keyring keyring) { + return keyring::getDecryptedData; + } } diff --git a/java/google/registry/keyring/api/Keyring.java b/java/google/registry/keyring/api/Keyring.java index 74ee8bead..1b7b583ca 100644 --- a/java/google/registry/keyring/api/Keyring.java +++ b/java/google/registry/keyring/api/Keyring.java @@ -22,8 +22,8 @@ import org.bouncycastle.openpgp.PGPPublicKey; /** * Nomulus keyring interface. * - *

Separate methods are defined for each specific situation in which the - * registry server needs a secret value, like a PGP key or password. + *

Separate methods are defined for each specific situation in which the registry server needs a + * secret value, like a PGP key or password. */ @ThreadSafe public interface Keyring extends AutoCloseable { @@ -31,11 +31,10 @@ public interface Keyring extends AutoCloseable { /** * Returns the key which should be used to sign RDE deposits being uploaded to a third-party. * - *

When we give all our data to the escrow provider, they'll need - * a signature to ensure the data is authentic. + *

When we give all our data to the escrow provider, they'll need a signature to ensure the + * data is authentic. * - *

This keypair should only be known to the domain registry shared - * registry system. + *

This keypair should only be known to the domain registry shared registry system. * * @see google.registry.rde.RdeUploadAction */ @@ -44,12 +43,10 @@ public interface Keyring extends AutoCloseable { /** * Returns public key for encrypting escrow deposits being staged to cloud storage. * - *

This adds an additional layer of security so cloud storage administrators - * won't be tempted to go poking around the App Engine Cloud Console and see a - * dump of the entire database. + *

This adds an additional layer of security so cloud storage administrators won't be tempted + * to go poking around the App Engine Cloud Console and see a dump of the entire database. * - *

This keypair should only be known to the domain registry shared - * registry system. + *

This keypair should only be known to the domain registry shared registry system. * * @see #getRdeStagingDecryptionKey() */ @@ -58,10 +55,9 @@ public interface Keyring extends AutoCloseable { /** * Returns private key for decrypting escrow deposits retrieved from cloud storage. * - *

This method may impose restrictions on who can call it. For example, we'd want - * to check that the caller isn't an HTTP request attacking a vulnerability in the - * admin console. The request should originate from a backend task queue servlet - * invocation of the RDE upload thing. + *

This method may impose restrictions on who can call it. For example, we'd want to check that + * the caller isn't an HTTP request attacking a vulnerability in the admin console. The request + * should originate from a backend task queue servlet invocation of the RDE upload thing. * * @see #getRdeStagingEncryptionKey() * @see google.registry.rde.RdeUploadAction @@ -92,9 +88,9 @@ public interface Keyring extends AutoCloseable { /** * Returns public key for SSH client connections made by RDE. * - *

This is a string containing what would otherwise be the contents of an - * {@code ~/.ssh/id_rsa.pub} file. It's usually a single line with the name of - * the algorithm, the base64 key, and the email address of the owner. + *

This is a string containing what would otherwise be the contents of an {@code + * ~/.ssh/id_rsa.pub} file. It's usually a single line with the name of the algorithm, the base64 + * key, and the email address of the owner. * * @see google.registry.rde.RdeUploadAction */ @@ -103,13 +99,12 @@ public interface Keyring extends AutoCloseable { /** * Returns private key for SSH client connections made by RDE. * - *

This is a string containing what would otherwise be the contents of an - * {@code ~/.ssh/id_rsa} file. It's ASCII-armored text. + *

This is a string containing what would otherwise be the contents of an {@code ~/.ssh/id_rsa} + * file. It's ASCII-armored text. * - *

This method may impose restrictions on who can call it. For example, we'd want - * to check that the caller isn't an HTTP request attacking a vulnerability in the - * admin console. The request should originate from a backend task queue servlet - * invocation of the RDE upload thing. + *

This method may impose restrictions on who can call it. For example, we'd want to check that + * the caller isn't an HTTP request attacking a vulnerability in the admin console. The request + * should originate from a backend task queue servlet invocation of the RDE upload thing. * * @see google.registry.rde.RdeUploadAction */ @@ -162,6 +157,9 @@ public interface Keyring extends AutoCloseable { */ String getEncryptedData(String keyName); + /** Decrypts the given encrypted data using the key name. */ + byte[] getDecryptedData(String keyName, String encryptedData); + // 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 06dbaf55e..c4d2f30d7 100644 --- a/java/google/registry/keyring/kms/KmsKeyring.java +++ b/java/google/registry/keyring/kms/KmsKeyring.java @@ -205,13 +205,27 @@ public class KmsKeyring implements Keyring { } private byte[] getDecryptedData(String keyName) { - KmsSecret secret = getSecret(keyName); + String encryptedData = getEncryptedData(keyName); + return getDecryptedData(keyName, encryptedData); + } + + private byte[] getDecryptedData(KmsSecret secret) { String encryptedData = getEncryptedData(secret); + return getDecryptedData(secret, encryptedData); + } + + private byte[] getDecryptedData(KmsSecret secret, String encryptedData) { try { return kmsConnection.decrypt(secret.getName(), encryptedData); } catch (Exception e) { throw new KeyringException( - String.format("CloudKMS decrypt operation failed for secret %s", keyName), e); + String.format("CloudKMS decrypt operation failed for secret %s", secret.getName()), e); } } + + @Override + public byte[] getDecryptedData(String keyName, String encryptedData) { + KmsSecret secret = getSecret(keyName); + return getDecryptedData(secret); + } } diff --git a/javatests/google/registry/keyring/kms/KmsKeyringTest.java b/javatests/google/registry/keyring/kms/KmsKeyringTest.java index 153bfaed9..94709f3f8 100644 --- a/javatests/google/registry/keyring/kms/KmsKeyringTest.java +++ b/javatests/google/registry/keyring/kms/KmsKeyringTest.java @@ -16,7 +16,7 @@ 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 static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.collect.ImmutableList; import com.google.common.io.BaseEncoding; @@ -26,7 +26,6 @@ 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; @@ -179,14 +178,26 @@ public class KmsKeyringTest { } @Test - public void test_getEncryptedJsonCredential() throws UnsupportedEncodingException { + public void test_getEncryptedJsonCredential() { saveCleartextSecret("json-credential-string"); String encryptedJsonCredential = keyring.getEncryptedData("json-credential-string"); assertThat( new String( - Arrays.reverse(BaseEncoding.base64().decode(encryptedJsonCredential)), US_ASCII)) + Arrays.reverse(BaseEncoding.base64().decode(encryptedJsonCredential)), UTF_8)) + .isEqualTo("json-credential-stringmoo"); + } + + @Test + public void test_decryptJsonCredential() { + saveCleartextSecret("json-credential-string"); + + String encryptedJsonCredential = keyring.getEncryptedData("json-credential-string"); + + assertThat( + new String( + keyring.getDecryptedData("json-credential-string", encryptedJsonCredential), UTF_8)) .isEqualTo("json-credential-stringmoo"); } diff --git a/javatests/google/registry/testing/FakeKeyringModule.java b/javatests/google/registry/testing/FakeKeyringModule.java index afb6887d4..916aad50c 100644 --- a/javatests/google/registry/testing/FakeKeyringModule.java +++ b/javatests/google/registry/testing/FakeKeyringModule.java @@ -156,6 +156,11 @@ public final class FakeKeyringModule { "Fake keyring does not support the retrieval of encrypted data."); } + @Override + public byte[] getDecryptedData(String keyName, String encryptedData) { + throw new RuntimeException("Fake keyring does not support decrypting of supplied data."); + } + @Override public void close() {} };