diff --git a/java/google/registry/proxy/CertificateModule.java b/java/google/registry/proxy/CertificateModule.java index 2a6e7fca3..a8ad05bac 100644 --- a/java/google/registry/proxy/CertificateModule.java +++ b/java/google/registry/proxy/CertificateModule.java @@ -19,10 +19,12 @@ import static com.google.common.collect.ImmutableList.toImmutableList; import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.collect.ImmutableList; +import dagger.Lazy; import dagger.Module; import dagger.Provides; -import google.registry.proxy.ProxyModule.PemBytes; +import google.registry.proxy.ProxyConfig.Environment; import google.registry.util.FormattingLogger; +import io.netty.handler.ssl.util.SelfSignedCertificate; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStreamReader; @@ -32,6 +34,7 @@ import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.function.Function; import javax.inject.Named; +import javax.inject.Qualifier; import javax.inject.Singleton; import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; @@ -44,18 +47,32 @@ import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; /** * Dagger module that provides bindings needed to inject EPP SSL certificate chain and private key. * - *

The certificates and private key are stored in a .pem file that is encrypted by Cloud KMS. The - * .pem file can be generated by concatenating the .crt certificate files on the chain and the .key - * private file. + *

The production certificates and private key are stored in a .pem file that is encrypted by + * Cloud KMS. The .pem file can be generated by concatenating the .crt certificate files on the + * chain and the .key private file. * - *

The certificates in the .pem file must be stored in order, where the next certificate's - * subject is the previous certificate's issuer. + *

The production certificates in the .pem file must be stored in order, where the next + * certificate's subject is the previous certificate's issuer. + * + *

When running the proxy locally or in test, a self signed certificate is used. * * @see Cloud Key Management Service */ @Module public class CertificateModule { + /** Dagger qualifier to provide bindings related to EPP certificates */ + @Qualifier + public @interface EppCertificates {} + + /** Dagger qualifier to provide bindings when running locally. */ + @Qualifier + public @interface Local {} + + /** Dagger qualifier to provide bindings when running in production. */ + @Qualifier + public @interface Prod {} + private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass(); static { @@ -79,12 +96,56 @@ public class CertificateModule { .collect(toImmutableList()); } + @Singleton + @Provides + @EppCertificates + static X509Certificate[] provideCertificates( + Environment env, + @Local Lazy localCertificates, + @Prod Lazy prodCertificates) { + return (env == Environment.LOCAL) ? localCertificates.get() : prodCertificates.get(); + } + + @Singleton + @Provides + @EppCertificates + static PrivateKey providePrivateKey( + Environment env, + @Local Lazy localPrivateKey, + @Prod Lazy prodPrivateKey) { + return (env == Environment.LOCAL) ? localPrivateKey.get() : prodPrivateKey.get(); + } + + @Singleton + @Provides + static SelfSignedCertificate provideSelfSignedCertificate() { + try { + return new SelfSignedCertificate(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Singleton + @Provides + @Local + static PrivateKey provideLocalPrivateKey(SelfSignedCertificate ssc) { + return ssc.key(); + } + + @Singleton + @Provides + @Local + static X509Certificate[] provideLocalCertificates(SelfSignedCertificate ssc) { + return new X509Certificate[] {ssc.cert()}; + } + @Singleton @Provides @Named("pemObjects") - static ImmutableList providePemObjects(PemBytes pemBytes) { + static ImmutableList providePemObjects(@Named("pemBytes") byte[] pemBytes) { PEMParser pemParser = - new PEMParser(new InputStreamReader(new ByteArrayInputStream(pemBytes.getBytes()), UTF_8)); + new PEMParser(new InputStreamReader(new ByteArrayInputStream(pemBytes), UTF_8)); ImmutableList.Builder listBuilder = new ImmutableList.Builder<>(); Object obj; // PEMParser returns an object (private key, certificate, etc) each time readObject() is called, @@ -107,7 +168,8 @@ public class CertificateModule { @Singleton @Provides - static PrivateKey providePrivateKey(@Named("pemObjects") ImmutableList pemObjects) { + @Prod + static PrivateKey provideProdPrivateKey(@Named("pemObjects") ImmutableList pemObjects) { JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC"); Function privateKeyConverter = pemKeyPair -> { @@ -129,8 +191,8 @@ public class CertificateModule { @Singleton @Provides - @Named("eppServerCertificates") - static X509Certificate[] provideCertificates( + @Prod + static X509Certificate[] provideProdCertificates( @Named("pemObjects") ImmutableList pemObject) { JcaX509CertificateConverter converter = new JcaX509CertificateConverter().setProvider("BC"); Function certificateConverter = diff --git a/java/google/registry/proxy/HttpsRelayProtocolModule.java b/java/google/registry/proxy/HttpsRelayProtocolModule.java index 230d0db1f..c575ae7da 100644 --- a/java/google/registry/proxy/HttpsRelayProtocolModule.java +++ b/java/google/registry/proxy/HttpsRelayProtocolModule.java @@ -28,7 +28,6 @@ import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.logging.LoggingHandler; import java.security.cert.X509Certificate; import javax.annotation.Nullable; -import javax.inject.Named; import javax.inject.Provider; import javax.inject.Qualifier; @@ -44,7 +43,7 @@ public class HttpsRelayProtocolModule { /** Dagger qualifier to provide https relay protocol related handlers and other bindings. */ @Qualifier - @interface HttpsRelayProtocol {} + public @interface HttpsRelayProtocol {} private static final String PROTOCOL_NAME = "https_relay"; @@ -89,7 +88,7 @@ public class HttpsRelayProtocolModule { @Nullable @Provides - @Named("relayTrustedCertificates") + @HttpsRelayProtocol public static X509Certificate[] provideTrustedCertificates() { // null uses the system default trust store. return null; diff --git a/java/google/registry/proxy/ProxyModule.java b/java/google/registry/proxy/ProxyModule.java index e78a6519f..a8964ec52 100644 --- a/java/google/registry/proxy/ProxyModule.java +++ b/java/google/registry/proxy/ProxyModule.java @@ -232,7 +232,8 @@ public class ProxyModule { @Singleton @Provides - static PemBytes providePemBytes( + @Named("pemBytes") + static byte[] providePemBytes( CloudKMS cloudKms, @Named("encryptedPemBytes") byte[] encryptedPemBytes, ProxyConfig config) { String cryptoKeyUrl = String.format( @@ -240,15 +241,14 @@ public class ProxyModule { config.projectId, config.kms.location, config.kms.keyRing, config.kms.cryptoKey); try { DecryptRequest decryptRequest = new DecryptRequest().encodeCiphertext(encryptedPemBytes); - return PemBytes.create( - cloudKms - .projects() - .locations() - .keyRings() - .cryptoKeys() - .decrypt(cryptoKeyUrl, decryptRequest) - .execute() - .decodePlaintext()); + return cloudKms + .projects() + .locations() + .keyRings() + .cryptoKeys() + .decrypt(cryptoKeyUrl, decryptRequest) + .execute() + .decodePlaintext(); } catch (IOException e) { logger.severefmt(e, "PEM file decryption failed using CryptoKey: %s", cryptoKeyUrl); throw new RuntimeException(e); @@ -283,31 +283,6 @@ public class ProxyModule { return getProxyConfig(env); } - /** - * A wrapper class for decrypted bytes of the PEM file. - * - *

Note that this should not be an @AutoValue class because we need a clone of the bytes to be - * returned, otherwise the wrapper class becomes mutable. - */ - // TODO: remove this class once FOSS build can use @BindsInstance to bind a byte[] - // (https://github.com/bazelbuild/bazel/issues/4138) - static class PemBytes { - - private final byte[] bytes; - - static PemBytes create(byte[] bytes) { - return new PemBytes(bytes); - } - - private PemBytes(byte[] bytes) { - this.bytes = bytes; - } - - byte[] getBytes() { - return bytes.clone(); - } - } - /** Root level component that exposes the port-to-protocol map. */ @Singleton @Component( diff --git a/java/google/registry/proxy/config/proxy-config-test.yaml b/java/google/registry/proxy/config/proxy-config-test.yaml deleted file mode 100644 index 70d433c07..000000000 --- a/java/google/registry/proxy/config/proxy-config-test.yaml +++ /dev/null @@ -1 +0,0 @@ -# This file is for test only. Leave it blank. diff --git a/java/google/registry/proxy/handler/SslClientInitializer.java b/java/google/registry/proxy/handler/SslClientInitializer.java index 419c06aab..792ae34f7 100644 --- a/java/google/registry/proxy/handler/SslClientInitializer.java +++ b/java/google/registry/proxy/handler/SslClientInitializer.java @@ -17,6 +17,7 @@ package google.registry.proxy.handler; import static com.google.common.base.Preconditions.checkNotNull; import static google.registry.proxy.Protocol.PROTOCOL_KEY; +import google.registry.proxy.HttpsRelayProtocolModule.HttpsRelayProtocol; import google.registry.proxy.Protocol.BackendProtocol; import google.registry.util.FormattingLogger; import io.netty.channel.Channel; @@ -29,7 +30,6 @@ import io.netty.handler.ssl.SslProvider; import java.security.cert.X509Certificate; import javax.annotation.Nullable; import javax.inject.Inject; -import javax.inject.Named; import javax.inject.Singleton; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLParameters; @@ -51,8 +51,7 @@ public class SslClientInitializer extends ChannelInitializer< @Inject SslClientInitializer( - SslProvider sslProvider, - @Nullable @Named("relayTrustedCertificates") X509Certificate... trustCertificates) { + SslProvider sslProvider, @Nullable @HttpsRelayProtocol X509Certificate... trustCertificates) { logger.infofmt("Client SSL Provider: %s", sslProvider); this.sslProvider = sslProvider; this.trustedCertificates = trustCertificates; diff --git a/java/google/registry/proxy/handler/SslServerInitializer.java b/java/google/registry/proxy/handler/SslServerInitializer.java index a7b54acdb..abb185326 100644 --- a/java/google/registry/proxy/handler/SslServerInitializer.java +++ b/java/google/registry/proxy/handler/SslServerInitializer.java @@ -14,6 +14,7 @@ package google.registry.proxy.handler; +import google.registry.proxy.CertificateModule.EppCertificates; import google.registry.util.FormattingLogger; import io.netty.channel.Channel; import io.netty.channel.ChannelHandler.Sharable; @@ -30,7 +31,6 @@ import io.netty.util.concurrent.Promise; import java.security.PrivateKey; import java.security.cert.X509Certificate; import javax.inject.Inject; -import javax.inject.Named; import javax.inject.Singleton; /** @@ -66,8 +66,8 @@ public class SslServerInitializer extends ChannelInitializer< @Inject SslServerInitializer( SslProvider sslProvider, - PrivateKey privateKey, - @Named("eppServerCertificates") X509Certificate... certificates) { + @EppCertificates PrivateKey privateKey, + @EppCertificates X509Certificate... certificates) { logger.infofmt("Server SSL Provider: %s", sslProvider); this.sslProvider = sslProvider; this.privateKey = privateKey; diff --git a/java/google/registry/repositories.bzl b/java/google/registry/repositories.bzl index b8765b46f..1535bff72 100644 --- a/java/google/registry/repositories.bzl +++ b/java/google/registry/repositories.bzl @@ -1147,10 +1147,10 @@ def com_google_code_findbugs_jsr305(): def com_google_dagger(): java_import_external( name = "com_google_dagger", - jar_sha256 = "b2142693bc7413f0b74330f0a92ca44ea95a12a22b659972ed6aa9832e8352e4", + jar_sha256 = "1f14720ffc3152a4207e374edb2ce114d94625058a6ef48a35cb67764dac4756", jar_urls = [ - "http://repo1.maven.org/maven2/com/google/dagger/dagger/2.13/dagger-2.13.jar", - "http://maven.ibiblio.org/maven2/com/google/dagger/dagger/2.13/dagger-2.13.jar", + "http://repo1.maven.org/maven2/com/google/dagger/dagger/2.15/dagger-2.15.jar", + "http://maven.ibiblio.org/maven2/com/google/dagger/dagger/2.15/dagger-2.15.jar", ], licenses = ["notice"], # Apache 2.0 deps = ["@javax_inject"], @@ -1170,10 +1170,10 @@ def com_google_dagger(): def com_google_dagger_compiler(): java_import_external( name = "com_google_dagger_compiler", - jar_sha256 = "8b711253c9cbb58bd2c019cb38afb32ee79f283e1bb3030c8c85b645c7a6d25f", + jar_sha256 = "ecd1e8ee0224312ae29203767fb8ec70af13e4f2724df2174ba0d2867cd2de78", jar_urls = [ - "http://maven.ibiblio.org/maven2/com/google/dagger/dagger-compiler/2.13/dagger-compiler-2.13.jar", - "http://repo1.maven.org/maven2/com/google/dagger/dagger-compiler/2.13/dagger-compiler-2.13.jar", + "http://maven.ibiblio.org/maven2/com/google/dagger/dagger-compiler/2.15/dagger-compiler-2.15.jar", + "http://repo1.maven.org/maven2/com/google/dagger/dagger-compiler/2.15/dagger-compiler-2.15.jar", ], licenses = ["notice"], # Apache 2.0 deps = [ @@ -1204,10 +1204,10 @@ def com_google_dagger_compiler(): def com_google_dagger_producers(): java_import_external( name = "com_google_dagger_producers", - jar_sha256 = "cf35b21c634939917eee9ffcd72a9f5f6e261ad57a4c0f0d15cf6f1430262bb0", + jar_sha256 = "eb189206f80df260de4331bb51e92a94e06f5cbf5ef3d1492d34c5e139e92eb1", jar_urls = [ - "http://repo1.maven.org/maven2/com/google/dagger/dagger-producers/2.13/dagger-producers-2.13.jar", - "http://maven.ibiblio.org/maven2/com/google/dagger/dagger-producers/2.13/dagger-producers-2.13.jar", + "http://repo1.maven.org/maven2/com/google/dagger/dagger-producers/2.15/dagger-producers-2.15.jar", + "http://maven.ibiblio.org/maven2/com/google/dagger/dagger-producers/2.15/dagger-producers-2.15.jar", ], licenses = ["notice"], # Apache 2.0 deps = [ diff --git a/javatests/google/registry/proxy/CertificateModuleTest.java b/javatests/google/registry/proxy/CertificateModuleTest.java index 5151440a2..841c0c265 100644 --- a/javatests/google/registry/proxy/CertificateModuleTest.java +++ b/javatests/google/registry/proxy/CertificateModuleTest.java @@ -20,9 +20,10 @@ import static google.registry.proxy.handler.SslInitializerTestUtils.signKeyPair; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.fail; -import dagger.BindsInstance; import dagger.Component; -import google.registry.proxy.ProxyModule.PemBytes; +import dagger.Module; +import dagger.Provides; +import google.registry.proxy.CertificateModule.Prod; import io.netty.handler.ssl.util.SelfSignedCertificate; import java.io.ByteArrayOutputStream; import java.io.OutputStreamWriter; @@ -59,9 +60,9 @@ public class CertificateModuleTest { } /** Create a component with bindings to the given bytes[] as the contents from a PEM file. */ - private TestComponent createComponent(byte[] bytes) { + private TestComponent createComponent(byte[] pemBytes) { return DaggerCertificateModuleTest_TestComponent.builder() - .pemBytes(PemBytes.create(bytes)) + .pemBytesModule(new PemBytesModule(pemBytes)) .build(); } @@ -137,22 +138,36 @@ public class CertificateModuleTest { } } - @Singleton - @Component(modules = {CertificateModule.class}) - interface TestComponent { + @Module + static class PemBytesModule { + private final byte[] pemBytes; - PrivateKey privateKey(); + PemBytesModule(byte[] pemBytes) { + this.pemBytes = pemBytes; + } - @Named("eppServerCertificates") - X509Certificate[] certificates(); - - @Component.Builder - interface Builder { - - @BindsInstance - Builder pemBytes(PemBytes pemBytes); - - TestComponent build(); + @Provides + @Named("pemBytes") + byte[] providePemBytes() { + return pemBytes; } } + + /** + * Test component that exposes prod certificate and key. + * + *

Local certificate and key are not tested because they are directly extracted from a + * self-signed certificate. Here we want to test that we can correctly parse and create + * certificate and keys from a .pem file. + */ + @Singleton + @Component(modules = {CertificateModule.class, PemBytesModule.class}) + interface TestComponent { + + @Prod + PrivateKey privateKey(); + + @Prod + X509Certificate[] certificates(); + } } diff --git a/javatests/google/registry/proxy/ProtocolModuleTest.java b/javatests/google/registry/proxy/ProtocolModuleTest.java index 7f1b222d8..161687bdd 100644 --- a/javatests/google/registry/proxy/ProtocolModuleTest.java +++ b/javatests/google/registry/proxy/ProtocolModuleTest.java @@ -15,7 +15,7 @@ package google.registry.proxy; import static com.google.common.collect.ImmutableList.toImmutableList; -import static google.registry.proxy.ProxyConfig.Environment.TEST; +import static google.registry.proxy.ProxyConfig.Environment.LOCAL; import static google.registry.proxy.ProxyConfig.getProxyConfig; import com.google.common.base.Suppliers; @@ -28,6 +28,7 @@ import dagger.Provides; import google.registry.proxy.EppProtocolModule.EppProtocol; import google.registry.proxy.HealthCheckProtocolModule.HealthCheckProtocol; import google.registry.proxy.HttpsRelayProtocolModule.HttpsRelayProtocol; +import google.registry.proxy.ProxyConfig.Environment; import google.registry.proxy.WhoisProtocolModule.WhoisProtocol; import google.registry.proxy.handler.BackendMetricsHandler; import google.registry.proxy.handler.ProxyProtocolHandler; @@ -45,10 +46,7 @@ import io.netty.channel.ChannelInitializer; import io.netty.channel.embedded.EmbeddedChannel; import io.netty.handler.logging.LoggingHandler; import io.netty.handler.ssl.SslProvider; -import io.netty.handler.ssl.util.SelfSignedCertificate; import io.netty.handler.timeout.ReadTimeoutHandler; -import java.security.PrivateKey; -import java.security.cert.X509Certificate; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -74,7 +72,7 @@ import org.junit.Before; */ public abstract class ProtocolModuleTest { - protected static final ProxyConfig PROXY_CONFIG = getProxyConfig(TEST); + protected static final ProxyConfig PROXY_CONFIG = getProxyConfig(LOCAL); protected TestComponent testComponent; @@ -179,6 +177,7 @@ public abstract class ProtocolModuleTest { @Component( modules = { TestModule.class, + CertificateModule.class, WhoisProtocolModule.class, EppProtocolModule.class, HealthCheckProtocolModule.class, @@ -224,7 +223,7 @@ public abstract class ProtocolModuleTest { @Singleton @Provides static ProxyConfig provideProxyConfig() { - return getProxyConfig(TEST); + return getProxyConfig(LOCAL); } @Singleton @@ -246,29 +245,6 @@ public abstract class ProtocolModuleTest { return new LoggingHandler(); } - @Singleton - @Provides - static SelfSignedCertificate provideSelfSignedCertificate() { - try { - return new SelfSignedCertificate(); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Singleton - @Provides - @Named("eppServerCertificates") - static X509Certificate[] provideCertificate(SelfSignedCertificate ssc) { - return new X509Certificate[] {ssc.cert()}; - } - - @Singleton - @Provides - static PrivateKey providePrivateKey(SelfSignedCertificate ssc) { - return ssc.key(); - } - @Singleton @Provides Clock provideFakeClock() { @@ -277,14 +253,29 @@ public abstract class ProtocolModuleTest { @Singleton @Provides - ExecutorService provideExecutorService() { + static ExecutorService provideExecutorService() { return MoreExecutors.newDirectExecutorService(); } @Singleton @Provides - ScheduledExecutorService provideScheduledExecutorService() { + static ScheduledExecutorService provideScheduledExecutorService() { return Executors.newSingleThreadScheduledExecutor(); } + + @Singleton + @Provides + static Environment provideEnvironment() { + return Environment.LOCAL; + } + + // This method is only here to satisfy Dagger binding, but is never used. In test environment, + // it is the self-signed certificate and its key that end up being used. + @Singleton + @Provides + @Named("pemBytes") + static byte[] providePemBytes() { + return new byte[0]; + } } } diff --git a/javatests/google/registry/proxy/ProxyModuleTest.java b/javatests/google/registry/proxy/ProxyModuleTest.java index 3bbb4373a..46fd10383 100644 --- a/javatests/google/registry/proxy/ProxyModuleTest.java +++ b/javatests/google/registry/proxy/ProxyModuleTest.java @@ -15,7 +15,7 @@ package google.registry.proxy; import static com.google.common.truth.Truth.assertThat; -import static google.registry.proxy.ProxyConfig.Environment.TEST; +import static google.registry.proxy.ProxyConfig.Environment.LOCAL; import static google.registry.proxy.ProxyConfig.getProxyConfig; import static google.registry.testing.JUnitBackports.assertThrows; import static org.junit.Assert.fail; @@ -30,7 +30,7 @@ import org.junit.runners.JUnit4; @RunWith(JUnit4.class) public class ProxyModuleTest { - private static final ProxyConfig PROXY_CONFIG = getProxyConfig(TEST); + private static final ProxyConfig PROXY_CONFIG = getProxyConfig(LOCAL); private final ProxyModule proxyModule = new ProxyModule(); @Test @@ -41,7 +41,7 @@ public class ProxyModuleTest { assertThat(proxyModule.provideEppPort(PROXY_CONFIG)).isEqualTo(PROXY_CONFIG.epp.port); assertThat(proxyModule.provideHealthCheckPort(PROXY_CONFIG)) .isEqualTo(PROXY_CONFIG.healthCheck.port); - assertThat(proxyModule.provideEnvironment()).isEqualTo(Environment.LOCAL); + assertThat(proxyModule.provideEnvironment()).isEqualTo(LOCAL); assertThat(proxyModule.log).isFalse(); }