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.");
}
@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() {}

View file

@ -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<String, String> provideEncryptedDataRetriever(Keyring keyring) {
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.
*
* <p>Separate methods are defined for each specific situation in which the
* registry server needs a secret value, like a PGP key or password.
* <p>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.
*
* <p>When we give all our data to the escrow provider, they'll need
* a signature to ensure the data is authentic.
* <p>When we give all our data to the escrow provider, they'll need a signature to ensure the
* data is authentic.
*
* <p>This keypair should only be known to the domain registry shared
* registry system.
* <p>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.
*
* <p>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.
* <p>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.
*
* <p>This keypair should only be known to the domain registry shared
* registry system.
* <p>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.
*
* <p>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.
* <p>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.
*
* <p>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.
* <p>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.
*
* <p>This is a string containing what would otherwise be the contents of an
* {@code ~/.ssh/id_rsa} file. It's ASCII-armored text.
* <p>This is a string containing what would otherwise be the contents of an {@code ~/.ssh/id_rsa}
* file. It's ASCII-armored text.
*
* <p>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.
* <p>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();

View file

@ -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);
}
}

View file

@ -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");
}

View file

@ -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() {}
};