Expose the functionality to decrypt given data using keyring

This allows us to provide the keyring a blob of encrypted data and a key name, and have it decrypt it for us.

Also fixed javadoc length in Keyring.java. It seems like it was using a 80-character length limit.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=222995542
This commit is contained in:
jianglai 2018-11-27 08:31:55 -08:00
parent 0ed0bcc99f
commit 4416601a1d
6 changed files with 70 additions and 30 deletions

View file

@ -155,6 +155,11 @@ public final class InMemoryKeyring implements Keyring {
"In-memory keyring does not support the retrieval of encrypted data."); "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. */ /** Does nothing. */
@Override @Override
public void close() {} public void close() {}

View file

@ -20,6 +20,7 @@ import dagger.Module;
import dagger.Provides; import dagger.Provides;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
import java.util.Optional; import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Function; import java.util.function.Function;
import javax.inject.Named; import javax.inject.Named;
import javax.inject.Qualifier; import javax.inject.Qualifier;
@ -134,4 +135,10 @@ public final class KeyModule {
static Function<String, String> provideEncryptedDataRetriever(Keyring keyring) { static Function<String, String> provideEncryptedDataRetriever(Keyring keyring) {
return keyring::getEncryptedData; return keyring::getEncryptedData;
} }
@Provides
@Named("keyringDecrypter")
static BiFunction<String, String, byte[]> provideKeyringDecrypter(Keyring keyring) {
return keyring::getDecryptedData;
}
} }

View file

@ -22,8 +22,8 @@ import org.bouncycastle.openpgp.PGPPublicKey;
/** /**
* Nomulus keyring interface. * Nomulus keyring interface.
* *
* <p>Separate methods are defined for each specific situation in which the * <p>Separate methods are defined for each specific situation in which the registry server needs a
* registry server needs a secret value, like a PGP key or password. * secret value, like a PGP key or password.
*/ */
@ThreadSafe @ThreadSafe
public interface Keyring extends AutoCloseable { 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. * Returns the key which should be used to sign RDE deposits being uploaded to a third-party.
* *
* <p>When we give all our data to the escrow provider, they'll need * <p>When we give all our data to the escrow provider, they'll need a signature to ensure the
* a signature to ensure the data is authentic. * data is authentic.
* *
* <p>This keypair should only be known to the domain registry shared * <p>This keypair should only be known to the domain registry shared registry system.
* registry system.
* *
* @see google.registry.rde.RdeUploadAction * @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. * Returns public key for encrypting escrow deposits being staged to cloud storage.
* *
* <p>This adds an additional layer of security so cloud storage administrators * <p>This adds an additional layer of security so cloud storage administrators won't be tempted
* won't be tempted to go poking around the App Engine Cloud Console and see a * to go poking around the App Engine Cloud Console and see a dump of the entire database.
* dump of the entire database.
* *
* <p>This keypair should only be known to the domain registry shared * <p>This keypair should only be known to the domain registry shared registry system.
* registry system.
* *
* @see #getRdeStagingDecryptionKey() * @see #getRdeStagingDecryptionKey()
*/ */
@ -58,10 +55,9 @@ public interface Keyring extends AutoCloseable {
/** /**
* Returns private key for decrypting escrow deposits retrieved from cloud storage. * Returns private key for decrypting escrow deposits retrieved from cloud storage.
* *
* <p>This method may impose restrictions on who can call it. For example, we'd want * <p>This method may impose restrictions on who can call it. For example, we'd want to check that
* to check that the caller isn't an HTTP request attacking a vulnerability in the * the caller isn't an HTTP request attacking a vulnerability in the admin console. The request
* admin console. The request should originate from a backend task queue servlet * should originate from a backend task queue servlet invocation of the RDE upload thing.
* invocation of the RDE upload thing.
* *
* @see #getRdeStagingEncryptionKey() * @see #getRdeStagingEncryptionKey()
* @see google.registry.rde.RdeUploadAction * @see google.registry.rde.RdeUploadAction
@ -92,9 +88,9 @@ public interface Keyring extends AutoCloseable {
/** /**
* Returns public key for SSH client connections made by RDE. * Returns public key for SSH client connections made by RDE.
* *
* <p>This is a string containing what would otherwise be the contents of an * <p>This is a string containing what would otherwise be the contents of an {@code
* {@code ~/.ssh/id_rsa.pub} file. It's usually a single line with the name of * ~/.ssh/id_rsa.pub} file. It's usually a single line with the name of the algorithm, the base64
* the algorithm, the base64 key, and the email address of the owner. * key, and the email address of the owner.
* *
* @see google.registry.rde.RdeUploadAction * @see google.registry.rde.RdeUploadAction
*/ */
@ -103,13 +99,12 @@ public interface Keyring extends AutoCloseable {
/** /**
* Returns private key for SSH client connections made by RDE. * Returns private key for SSH client connections made by RDE.
* *
* <p>This is a string containing what would otherwise be the contents of an * <p>This is a string containing what would otherwise be the contents of an {@code ~/.ssh/id_rsa}
* {@code ~/.ssh/id_rsa} file. It's ASCII-armored text. * file. It's ASCII-armored text.
* *
* <p>This method may impose restrictions on who can call it. For example, we'd want * <p>This method may impose restrictions on who can call it. For example, we'd want to check that
* to check that the caller isn't an HTTP request attacking a vulnerability in the * the caller isn't an HTTP request attacking a vulnerability in the admin console. The request
* admin console. The request should originate from a backend task queue servlet * should originate from a backend task queue servlet invocation of the RDE upload thing.
* invocation of the RDE upload thing.
* *
* @see google.registry.rde.RdeUploadAction * @see google.registry.rde.RdeUploadAction
*/ */
@ -162,6 +157,9 @@ public interface Keyring extends AutoCloseable {
*/ */
String getEncryptedData(String keyName); 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. // Don't throw so try-with-resources works better.
@Override @Override
void close(); void close();

View file

@ -205,13 +205,27 @@ public class KmsKeyring implements Keyring {
} }
private byte[] getDecryptedData(String keyName) { 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); String encryptedData = getEncryptedData(secret);
return getDecryptedData(secret, encryptedData);
}
private byte[] getDecryptedData(KmsSecret secret, String encryptedData) {
try { try {
return kmsConnection.decrypt(secret.getName(), encryptedData); return kmsConnection.decrypt(secret.getName(), encryptedData);
} catch (Exception e) { } catch (Exception e) {
throw new KeyringException( 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);
}
} }

View file

@ -16,7 +16,7 @@ package google.registry.keyring.kms;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static google.registry.testing.DatastoreHelper.persistResources; 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.collect.ImmutableList;
import com.google.common.io.BaseEncoding; 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.model.server.KmsSecretRevision.Builder;
import google.registry.testing.AppEngineRule; import google.registry.testing.AppEngineRule;
import google.registry.testing.BouncyCastleProviderRule; import google.registry.testing.BouncyCastleProviderRule;
import java.io.UnsupportedEncodingException;
import org.bouncycastle.openpgp.PGPKeyPair; import org.bouncycastle.openpgp.PGPKeyPair;
import org.bouncycastle.openpgp.PGPPrivateKey; import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPPublicKey;
@ -179,14 +178,26 @@ public class KmsKeyringTest {
} }
@Test @Test
public void test_getEncryptedJsonCredential() throws UnsupportedEncodingException { public void test_getEncryptedJsonCredential() {
saveCleartextSecret("json-credential-string"); saveCleartextSecret("json-credential-string");
String encryptedJsonCredential = keyring.getEncryptedData("json-credential-string"); String encryptedJsonCredential = keyring.getEncryptedData("json-credential-string");
assertThat( assertThat(
new String( 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"); .isEqualTo("json-credential-stringmoo");
} }

View file

@ -156,6 +156,11 @@ public final class FakeKeyringModule {
"Fake keyring does not support the retrieval of encrypted data."); "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 @Override
public void close() {} public void close() {}
}; };