diff --git a/core/src/main/java/com/google/cloud/sql/core/ConscryptWorkaroundDelegatingTrustManger.java b/core/src/main/java/com/google/cloud/sql/core/ConscryptWorkaroundDelegatingTrustManger.java index 3caa91235..66e9b1d13 100644 --- a/core/src/main/java/com/google/cloud/sql/core/ConscryptWorkaroundDelegatingTrustManger.java +++ b/core/src/main/java/com/google/cloud/sql/core/ConscryptWorkaroundDelegatingTrustManger.java @@ -17,8 +17,12 @@ package com.google.cloud.sql.core; import java.net.Socket; +import java.security.NoSuchAlgorithmException; +import java.security.Provider; +import java.security.Security; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; +import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import javax.net.ssl.X509ExtendedTrustManager; @@ -33,6 +37,30 @@ *

See https://github.com/google/conscrypt/issues/1033#issuecomment-982701272 */ class ConscryptWorkaroundDelegatingTrustManger extends X509ExtendedTrustManager { + private static final boolean CONSCRYPT_TLS; + + static { + // Provider name is "Conscrypt", hardcoded string in the library source: + // https://github.com/google/conscrypt/blob/655ad5069e1cb4d1989b8117eaf090371885af99/openjdk/src/main/java/org/conscrypt/Platform.java#L149 + Provider p = Security.getProvider("Conscrypt"); + if (p != null) { + try { + SSLContext ctx = SSLContext.getInstance("TLS"); + Provider prov = ctx.getProvider(); + CONSCRYPT_TLS = "Conscrypt".equals(prov.getName()); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("Unable to load algorithm TLS", e); + } + } else { + CONSCRYPT_TLS = false; + } + } + + /** Returns true if the Conscrypt Java Crypto Extension is installed. */ + static boolean isWorkaroundNeeded() { + return CONSCRYPT_TLS; + } + private final X509ExtendedTrustManager tm; ConscryptWorkaroundDelegatingTrustManger(X509ExtendedTrustManager tm) { diff --git a/core/src/main/java/com/google/cloud/sql/core/ConscryptWorkaroundTrustManagerFactory.java b/core/src/main/java/com/google/cloud/sql/core/ConscryptWorkaroundTrustManagerFactory.java deleted file mode 100644 index 2e87088fe..000000000 --- a/core/src/main/java/com/google/cloud/sql/core/ConscryptWorkaroundTrustManagerFactory.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2024 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.cloud.sql.core; - -import java.security.NoSuchAlgorithmException; -import java.security.Provider; -import java.security.Security; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManagerFactory; - -/** - * This is a workaround for a known bug in Conscrypt crypto in how it handles X509 auth type. - * OpenJDK interpres the X509 certificate auth type as "UNKNOWN" while Conscrypt interpret the same - * certificate as auth type "GENERIC". This incompatibility causes problems in the JDK. - * - *

This adapter works around the issue by creating wrappers around all TrustManager instances. It - * replaces "GENERIC" auth type with "UNKNOWN" auth type before delegating calls. - * - *

In the JVM, we need to implement 3 classes to make sure that we are capturing all of the - * TrustManager instances created when Conscrypt is the Java Crypto provider so that we can wrap - * them with the ConscryptWorkaroundTrustManager: - * - *

class ConscryptWorkaroundTrustManagerFactory extends TrustManagerFactory - has a bunch of - * final methods that delegate to a TrustManagerFactorySpi. class - * ConscryptWorkaroundTrustManagerFactorySpi implements TrustManagerFactorySpi - can actually - * intercept and delegate calls related to trust managers and wrap them with - * ConscryptWorkaroundTrustManager ConscryptWorkaroundTrustManager - the workaround for the - * Conscrypt bug. - * - *

See https://github.com/google/conscrypt/issues/1033#issuecomment-982701272 - */ -class ConscryptWorkaroundTrustManagerFactory extends TrustManagerFactory { - private static final boolean CONSCRYPT_TLS; - - static { - // Provider name is "Conscrypt", hardcoded string in the library source: - // https://github.com/google/conscrypt/blob/655ad5069e1cb4d1989b8117eaf090371885af99/openjdk/src/main/java/org/conscrypt/Platform.java#L149 - Provider p = Security.getProvider("Conscrypt"); - if (p != null) { - try { - SSLContext ctx = SSLContext.getInstance("TLS"); - Provider prov = ctx.getProvider(); - CONSCRYPT_TLS = "Conscrypt".equals(prov.getName()); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException("Unable to load algorithm TLS", e); - } - } else { - CONSCRYPT_TLS = false; - } - } - - /** Returns true if the Conscrypt Java Crypto Extension is installed. */ - static boolean isWorkaroundNeeded() { - return CONSCRYPT_TLS; - } - - static ConscryptWorkaroundTrustManagerFactory newInstance() throws NoSuchAlgorithmException { - TrustManagerFactory delegate = TrustManagerFactory.getInstance("X.509"); - return new ConscryptWorkaroundTrustManagerFactory(delegate); - } - - private ConscryptWorkaroundTrustManagerFactory(TrustManagerFactory delegate) { - super( - new ConscryptWorkaroundTrustManagerFactorySpi(delegate), - delegate.getProvider(), - delegate.getAlgorithm()); - } -} diff --git a/core/src/main/java/com/google/cloud/sql/core/DefaultConnectionInfoRepository.java b/core/src/main/java/com/google/cloud/sql/core/DefaultConnectionInfoRepository.java index eed705ba1..ff77fc764 100644 --- a/core/src/main/java/com/google/cloud/sql/core/DefaultConnectionInfoRepository.java +++ b/core/src/main/java/com/google/cloud/sql/core/DefaultConnectionInfoRepository.java @@ -379,22 +379,8 @@ private SslData createSslData( KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); kmf.init(authKeyStore, new char[0]); - KeyStore trustedKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()); - trustedKeyStore.load(null, null); - trustedKeyStore.setCertificateEntry("instance", instanceMetadata.getInstanceCaCertificate()); - TrustManagerFactory tmf; - - // Note: This is a workaround for Conscrypt bug #1033 - // Conscrypt is the JCE provider on some Google Cloud runtimes like DataProc. - // https://github.com/google/conscrypt/issues/1033 - // - if (ConscryptWorkaroundTrustManagerFactory.isWorkaroundNeeded()) { - tmf = ConscryptWorkaroundTrustManagerFactory.newInstance(); - } else { - tmf = TrustManagerFactory.getInstance("X.509"); - } - - tmf.init(trustedKeyStore); + TrustManagerFactory tmf = + InstanceCheckingTrustManagerFactory.newInstance(instanceName, instanceMetadata); SSLContext sslContext; diff --git a/core/src/main/java/com/google/cloud/sql/core/InstanceCheckingTrustManagerFactory.java b/core/src/main/java/com/google/cloud/sql/core/InstanceCheckingTrustManagerFactory.java new file mode 100644 index 000000000..22b1ee221 --- /dev/null +++ b/core/src/main/java/com/google/cloud/sql/core/InstanceCheckingTrustManagerFactory.java @@ -0,0 +1,70 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.sql.core; + +import java.io.IOException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import javax.net.ssl.TrustManagerFactory; + +/** + * Implement custom server certificate trust checks specific to Cloud SQL. + * + *

In the JVM, we need to implement 3 classes to make sure that we are capturing all the + * TrustManager instances created by the Java Crypto provider so that the connector will: + * + *

class InstanceCheckingTrustManagerFactory extends TrustManagerFactory - has a bunch of final + * methods that delegate to a TrustManagerFactorySpi. + * + *

class InstanceCheckingTrustManagerFactorySpi implements TrustManagerFactorySpi - can actually + * intercept requests for the list of TrustManager instances and wrap them with our replacement + * TrustManager. + * + *

class ConscryptWorkaroundTrustManager - the workaround for the Conscrypt bug. + * + *

class InstanceCheckingTrustManager - delegates TLS checks to the default provider and then + * checks that the Subject CN field contains the Cloud SQL instance ID. + */ +class InstanceCheckingTrustManagerFactory extends TrustManagerFactory { + + static InstanceCheckingTrustManagerFactory newInstance( + CloudSqlInstanceName instanceName, InstanceMetadata instanceMetadata) + throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException { + + TrustManagerFactory delegate = TrustManagerFactory.getInstance("X.509"); + KeyStore trustedKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + trustedKeyStore.load(null, null); + trustedKeyStore.setCertificateEntry("instance", instanceMetadata.getInstanceCaCertificate()); + + InstanceCheckingTrustManagerFactory tmf = + new InstanceCheckingTrustManagerFactory(instanceName, delegate); + + tmf.init(trustedKeyStore); + + return tmf; + } + + private InstanceCheckingTrustManagerFactory( + CloudSqlInstanceName instanceName, TrustManagerFactory delegate) { + super( + new InstanceCheckingTrustManagerFactorySpi(instanceName, delegate), + delegate.getProvider(), + delegate.getAlgorithm()); + } +} diff --git a/core/src/main/java/com/google/cloud/sql/core/ConscryptWorkaroundTrustManagerFactorySpi.java b/core/src/main/java/com/google/cloud/sql/core/InstanceCheckingTrustManagerFactorySpi.java similarity index 65% rename from core/src/main/java/com/google/cloud/sql/core/ConscryptWorkaroundTrustManagerFactorySpi.java rename to core/src/main/java/com/google/cloud/sql/core/InstanceCheckingTrustManagerFactorySpi.java index 36408a756..407de9c41 100644 --- a/core/src/main/java/com/google/cloud/sql/core/ConscryptWorkaroundTrustManagerFactorySpi.java +++ b/core/src/main/java/com/google/cloud/sql/core/InstanceCheckingTrustManagerFactorySpi.java @@ -26,19 +26,16 @@ import javax.net.ssl.X509ExtendedTrustManager; /** - * This is a workaround for a known bug in Conscrypt crypto in how it handles X509 auth type. - * OpenJDK interpres the X509 certificate auth type as "UNKNOWN" while Conscrypt interpret the same - * certificate as auth type "GENERIC". This incompatibility causes problems in the JDK. - * - *

This adapter works around the issue by creating wrappers around all TrustManager instances. It - * replaces "GENERIC" auth type with "UNKNOWN" auth type before delegating calls. - * - *

See https://github.com/google/conscrypt/issues/1033#issuecomment-982701272 + * Part of the InstanceCheckingTrustManagerFactory that implements custom CloudSQL server + * certificate checks. */ -class ConscryptWorkaroundTrustManagerFactorySpi extends TrustManagerFactorySpi { +class InstanceCheckingTrustManagerFactorySpi extends TrustManagerFactorySpi { private final TrustManagerFactory delegate; + private final CloudSqlInstanceName instanceName; - ConscryptWorkaroundTrustManagerFactorySpi(TrustManagerFactory delegate) { + InstanceCheckingTrustManagerFactorySpi( + CloudSqlInstanceName instanceName, TrustManagerFactory delegate) { + this.instanceName = instanceName; this.delegate = delegate; } @@ -59,10 +56,17 @@ protected TrustManager[] engineGetTrustManagers() { TrustManager[] delegates = new TrustManager[tms.length]; for (int i = 0; i < tms.length; i++) { if (tms[i] instanceof X509ExtendedTrustManager) { - delegates[i] = - new ConscryptWorkaroundDelegatingTrustManger((X509ExtendedTrustManager) tms[i]); - } else { + X509ExtendedTrustManager tm = (X509ExtendedTrustManager) tms[i]; + // Note: This is a workaround for Conscrypt bug #1033 + // Conscrypt is the JCE provider on some Google Cloud runtimes like DataProc. + // https://github.com/google/conscrypt/issues/1033 + if (ConscryptWorkaroundDelegatingTrustManger.isWorkaroundNeeded()) { + tm = new ConscryptWorkaroundDelegatingTrustManger(tm); + } + + delegates[i] = new InstanceCheckingTrustManger(instanceName, tm); + } else { delegates[i] = tms[i]; } } diff --git a/core/src/main/java/com/google/cloud/sql/core/InstanceCheckingTrustManger.java b/core/src/main/java/com/google/cloud/sql/core/InstanceCheckingTrustManger.java new file mode 100644 index 000000000..69d41d8d2 --- /dev/null +++ b/core/src/main/java/com/google/cloud/sql/core/InstanceCheckingTrustManger.java @@ -0,0 +1,128 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.sql.core; + +import java.net.Socket; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import javax.naming.InvalidNameException; +import javax.naming.ldap.LdapName; +import javax.naming.ldap.Rdn; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.X509ExtendedTrustManager; + +/** + * This is a workaround for a known bug in Conscrypt crypto in how it handles X509 auth type. + * OpenJDK interpres the X509 certificate auth type as "UNKNOWN" while Conscrypt interpret the same + * certificate as auth type "GENERIC". This incompatibility causes problems in the JDK. + * + *

This adapter works around the issue by creating wrappers around all TrustManager instances. It + * replaces "GENERIC" auth type with "UNKNOWN" auth type before delegating calls. + * + *

See https://github.com/google/conscrypt/issues/1033#issuecomment-982701272 + */ +class InstanceCheckingTrustManger extends X509ExtendedTrustManager { + private final X509ExtendedTrustManager tm; + private final CloudSqlInstanceName instanceName; + + public InstanceCheckingTrustManger( + CloudSqlInstanceName instanceName, X509ExtendedTrustManager tm) { + this.instanceName = instanceName; + this.tm = tm; + } + + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket) + throws CertificateException { + tm.checkClientTrusted(chain, authType, socket); + } + + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine) + throws CertificateException { + tm.checkClientTrusted(chain, authType, engine); + } + + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) + throws CertificateException { + tm.checkClientTrusted(chain, authType); + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket) + throws CertificateException { + tm.checkServerTrusted(chain, authType, socket); + checkCertificateChain(chain); + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine engine) + throws CertificateException { + tm.checkServerTrusted(chain, authType, engine); + checkCertificateChain(chain); + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) + throws CertificateException { + tm.checkServerTrusted(chain, authType); + checkCertificateChain(chain); + } + + private void checkCertificateChain(X509Certificate[] chain) throws CertificateException { + if (chain.length == 0) { + throw new CertificateException("No server certificates in chain"); + } + if (chain[0].getSubjectX500Principal() == null) { + throw new CertificateException("Subject is missing"); + } + + String cn = null; + + try { + String subject = chain[0].getSubjectX500Principal().getName(); + LdapName subjectName = new LdapName(subject); + for (Rdn rdn : subjectName.getRdns()) { + if ("CN".equals(rdn.getType())) { + cn = (String) rdn.getValue(); + } + } + } catch (InvalidNameException e) { + throw new CertificateException("Exception parsing the server certificate subject field", e); + } + + if (cn == null) { + throw new CertificateException("Server certificate subject does not contain a value for CN"); + } + + // parse CN from subject. CN always comes last in the list. + String instName = this.instanceName.getProjectId() + ":" + this.instanceName.getInstanceId(); + if (!instName.equals(cn)) { + throw new CertificateException( + "Server certificate CN does not match instance name. Server certificate CN=" + + cn + + " Expected instance name: " + + instName); + } + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return tm.getAcceptedIssuers(); + } +} diff --git a/core/src/test/java/com/google/cloud/sql/core/CloudSqlCoreTestingBase.java b/core/src/test/java/com/google/cloud/sql/core/CloudSqlCoreTestingBase.java index fb1fca9e0..ffc371f9d 100644 --- a/core/src/test/java/com/google/cloud/sql/core/CloudSqlCoreTestingBase.java +++ b/core/src/test/java/com/google/cloud/sql/core/CloudSqlCoreTestingBase.java @@ -62,6 +62,8 @@ public class CloudSqlCoreTestingBase { ListenableFuture clientKeyPair; + public CloudSqlCoreTestingBase() {} + // Creates a fake "accessNotConfigured" exception that can be used for testing. static HttpTransport fakeNotConfiguredException() { return fakeGoogleJsonResponseException( @@ -130,10 +132,18 @@ public void setup() throws GeneralSecurityException { } HttpTransport fakeSuccessHttpTransport(Duration certDuration) { - return fakeSuccessHttpTransport(certDuration, null); + return fakeSuccessHttpTransport(TestKeys.getServerCertPem(), certDuration, null); } HttpTransport fakeSuccessHttpTransport(Duration certDuration, String baseUrl) { + return fakeSuccessHttpTransport(TestKeys.getServerCertPem(), certDuration, baseUrl); + } + + HttpTransport fakeSuccessHttpTransport(String serverCert, Duration certDuration) { + return fakeSuccessHttpTransport(serverCert, certDuration, null); + } + + HttpTransport fakeSuccessHttpTransport(String serverCert, Duration certDuration, String baseUrl) { final JsonFactory jsonFactory = new GsonFactory(); return new MockHttpTransport() { @Override @@ -153,7 +163,7 @@ public LowLevelHttpResponse execute() throws IOException { ImmutableList.of( new IpMapping().setIpAddress(PUBLIC_IP).setType("PRIMARY"), new IpMapping().setIpAddress(PRIVATE_IP).setType("PRIVATE"))) - .setServerCaCert(new SslCert().setCert(TestKeys.getServerCertPem())) + .setServerCaCert(new SslCert().setCert(serverCert)) .setDatabaseVersion("POSTGRES14") .setRegion("myRegion"); settings.setFactory(jsonFactory); diff --git a/core/src/test/java/com/google/cloud/sql/core/ConnectorTest.java b/core/src/test/java/com/google/cloud/sql/core/ConnectorTest.java index aea3973da..13199931e 100644 --- a/core/src/test/java/com/google/cloud/sql/core/ConnectorTest.java +++ b/core/src/test/java/com/google/cloud/sql/core/ConnectorTest.java @@ -159,18 +159,34 @@ public void create_successfulUnixSocketConnection() throws IOException, Interrup @Test public void create_successfulDomainScopedConnection() throws IOException, InterruptedException { - FakeSslServer sslServer = new FakeSslServer(); + FakeSslServer sslServer = + new FakeSslServer( + TestKeys.getDomainServerKeyPair().getPrivate(), TestKeys.getDomainServerCert()); + CredentialFactoryProvider credentialFactoryProvider = + new CredentialFactoryProvider(new StubCredentialFactory("foo", null)); + ConnectionInfoRepositoryFactory factory = + new StubConnectionInfoRepositoryFactory( + fakeSuccessHttpTransport(TestKeys.getDomainServerCertPem(), Duration.ofSeconds(60))); + + int port = sslServer.start(PUBLIC_IP); ConnectionConfig config = new ConnectionConfig.Builder() .withCloudSqlInstance("example.com:myProject:myRegion:myInstance") .withIpTypes("PRIMARY") .build(); + Connector c = + new Connector( + config.getConnectorConfig(), + factory, + credentialFactoryProvider.getInstanceCredentialFactory(config.getConnectorConfig()), + defaultExecutor, + clientKeyPair, + 10, + TEST_MAX_REFRESH_MS, + port); - int port = sslServer.start(PUBLIC_IP); - - Connector connector = newConnector(config.getConnectorConfig(), port); + Socket socket = c.connect(config, TEST_MAX_REFRESH_MS); - Socket socket = connector.connect(config, TEST_MAX_REFRESH_MS); assertThat(readLine(socket)).isEqualTo(SERVER_MESSAGE); } @@ -312,7 +328,8 @@ public void supportsCustomCredentialFactoryWithIAM() throws InterruptedException new CredentialFactoryProvider( new StubCredentialFactory("foo", Instant.now().plusSeconds(3600).toEpochMilli())); ConnectionInfoRepositoryFactory factory = - new StubConnectionInfoRepositoryFactory(fakeSuccessHttpTransport(Duration.ofSeconds(0))); + new StubConnectionInfoRepositoryFactory( + fakeSuccessHttpTransport(TestKeys.getServerCertPem(), Duration.ofSeconds(0))); int port = sslServer.start(PUBLIC_IP); ConnectionConfig config = diff --git a/core/src/test/java/com/google/cloud/sql/core/FakeSslServer.java b/core/src/test/java/com/google/cloud/sql/core/FakeSslServer.java index 579008a66..10a945b3e 100644 --- a/core/src/test/java/com/google/cloud/sql/core/FakeSslServer.java +++ b/core/src/test/java/com/google/cloud/sql/core/FakeSslServer.java @@ -45,6 +45,11 @@ public class FakeSslServer { cert = TestKeys.getServerCert(); } + public FakeSslServer(PrivateKey privateKey, X509Certificate cert) { + this.privateKey = privateKey; + this.cert = cert; + } + int start(final String ip) throws InterruptedException { final CountDownLatch countDownLatch = new CountDownLatch(1); final AtomicInteger pickedPort = new AtomicInteger(); diff --git a/core/src/test/java/com/google/cloud/sql/core/TestCertificateGenerator.java b/core/src/test/java/com/google/cloud/sql/core/TestCertificateGenerator.java index 4976edbdc..08cfcedd8 100644 --- a/core/src/test/java/com/google/cloud/sql/core/TestCertificateGenerator.java +++ b/core/src/test/java/com/google/cloud/sql/core/TestCertificateGenerator.java @@ -68,15 +68,19 @@ public class TestCertificateGenerator { private static final X500Name SERVER_CERT_SUBJECT = new X500Name("C=US,O=Google\\, Inc,CN=myProject:myInstance"); + private static final X500Name DOMAIN_SERVER_CERT_SUBJECT = + new X500Name("C=US,O=Google\\, Inc,CN=example.com:myProject:myInstance"); private final String SHA_256_WITH_RSA = "SHA256WithRSA"; private final KeyPair signingCaKeyPair; private final KeyPair serverCaKeyPair; + private final KeyPair domainServerKeyPair; private final KeyPair serverKeyPair; private final KeyPair clientKeyPair; private final X509Certificate signingCaCert; private final X509Certificate serverCaCert; private final X509Certificate serverCertificate; + private final X509Certificate domainServerCertificate; private final String PEM_HEADER = "-----BEGIN CERTIFICATE-----"; private final String PEM_FOOTER = "-----END CERTIFICATE-----"; @@ -101,6 +105,7 @@ static KeyPair generateKeyPair() { this.signingCaKeyPair = generateKeyPair(); this.serverKeyPair = generateKeyPair(); this.clientKeyPair = generateKeyPair(); + this.domainServerKeyPair = generateKeyPair(); try { this.serverCaCert = buildRootCertificate(SERVER_CA_SUBJECT, this.serverCaKeyPair); @@ -114,6 +119,15 @@ static KeyPair generateKeyPair() { serverCaKeyPair.getPrivate(), ONE_YEAR_FROM_NOW, null); + + this.domainServerCertificate = + buildSignedCertificate( + DOMAIN_SERVER_CERT_SUBJECT, + domainServerKeyPair.getPublic(), + SERVER_CA_SUBJECT, + serverCaKeyPair.getPrivate(), + ONE_YEAR_FROM_NOW, + null); } catch (OperatorCreationException | CertificateException | IOException e) { throw new RuntimeException(e); } @@ -135,6 +149,14 @@ public X509Certificate getServerCaCert() { return serverCaCert; } + public KeyPair getDomainServerKeyPair() { + return domainServerKeyPair; + } + + public X509Certificate getDomainServerCertificate() { + return domainServerCertificate; + } + public X509Certificate createEphemeralCert(String cn, Duration shiftIntoPast) throws GeneralSecurityException, ExecutionException, OperatorCreationException { Duration validFor = Duration.ofHours(1); diff --git a/core/src/test/java/com/google/cloud/sql/core/TestKeys.java b/core/src/test/java/com/google/cloud/sql/core/TestKeys.java index 1bee391af..c8a1843cb 100644 --- a/core/src/test/java/com/google/cloud/sql/core/TestKeys.java +++ b/core/src/test/java/com/google/cloud/sql/core/TestKeys.java @@ -66,4 +66,16 @@ public static String createEphemeralCert(Duration certDuration) { certs.getEphemeralCertificate( "temporary-cert", certs.getClientKey().getPublic(), notAfter.toInstant())); } + + public static KeyPair getDomainServerKeyPair() { + return certs.getDomainServerKeyPair(); + } + + public static X509Certificate getDomainServerCert() { + return certs.getDomainServerCertificate(); + } + + public static String getDomainServerCertPem() { + return certs.getPemForCert(certs.getDomainServerCertificate()); + } }