diff --git a/core/src/main/java/com/google/cloud/sql/core/Connector.java b/core/src/main/java/com/google/cloud/sql/core/Connector.java index 14379fcee..7bbf28547 100644 --- a/core/src/main/java/com/google/cloud/sql/core/Connector.java +++ b/core/src/main/java/com/google/cloud/sql/core/Connector.java @@ -118,6 +118,7 @@ Socket connect(ConnectionConfig config, long timeoutMs) throws IOException { SSLSocket socket = (SSLSocket) metadata.getSslContext().getSocketFactory().createSocket(); socket.setKeepAlive(true); socket.setTcpNoDelay(true); + socket.connect(new InetSocketAddress(instanceIp, serverProxyPort)); try { 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 7ebff2498..154d8bf17 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 @@ -45,9 +45,9 @@ import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.time.Instant; +import java.util.ArrayList; import java.util.Arrays; import java.util.Base64; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -84,10 +84,15 @@ private void checkDatabaseCompatibility( } // Creates a Certificate object from a provided string. - private Certificate createCertificate(String cert) throws CertificateException { + private List parseCertificateChain(String cert) throws CertificateException { byte[] certBytes = cert.getBytes(StandardCharsets.UTF_8); ByteArrayInputStream certStream = new ByteArrayInputStream(certBytes); - return CertificateFactory.getInstance("X.509").generateCertificate(certStream); + List certificates = new ArrayList<>(); + while (certStream.available() > 0) { + Certificate c = CertificateFactory.getInstance("X.509").generateCertificate(certStream); + certificates.add(c); + } + return certificates; } private String generatePublicKeyCert(KeyPair keyPair) { @@ -296,18 +301,17 @@ private InstanceMetadata fetchMetadata(CloudSqlInstanceName instanceName, AuthTy + "IP address.", instanceName.getConnectionName())); } - // Update the Server CA certificate used to create the SSL connection with the instance. try { - Certificate instanceCaCertificate = - createCertificate(instanceMetadata.getServerCaCert().getCert()); + List instanceCaCertificates = + parseCertificateChain(instanceMetadata.getServerCaCert().getCert()); logger.debug(String.format("[%s] METADATA DONE", instanceName)); return new InstanceMetadata( instanceName, ipAddrs, - Collections.singletonList(instanceCaCertificate), + instanceCaCertificates, "GOOGLE_MANAGED_CAS_CA".equals(instanceMetadata.getServerCaMode()), instanceMetadata.getDnsName(), pscEnabled); @@ -371,7 +375,9 @@ private Certificate fetchEphemeralCertificate( // Parse the certificate from the response. Certificate ephemeralCertificate; try { - ephemeralCertificate = createCertificate(response.getEphemeralCert().getCert()); + // The response contains a single certificate. This uses the parseCertificateChain method + // to parse the response, and then uses the first, and only, certificate. + ephemeralCertificate = parseCertificateChain(response.getEphemeralCert().getCert()).get(0); } catch (CertificateException ex) { throw new RuntimeException( String.format( @@ -407,8 +413,7 @@ private SslData createSslData( KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); kmf.init(authKeyStore, new char[0]); - TrustManagerFactory tmf = - InstanceCheckingTrustManagerFactory.newInstance(instanceName, instanceMetadata); + TrustManagerFactory tmf = InstanceCheckingTrustManagerFactory.newInstance(instanceMetadata); SSLContext sslContext; @@ -428,7 +433,6 @@ private SslData createSslData( sslContext = SSLContext.getInstance("TLSv1.2"); } } - sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom()); logger.debug( 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 index 459047abc..2266e4a5d 100644 --- a/core/src/main/java/com/google/cloud/sql/core/InstanceCheckingTrustManagerFactory.java +++ b/core/src/main/java/com/google/cloud/sql/core/InstanceCheckingTrustManagerFactory.java @@ -20,6 +20,7 @@ import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; +import java.security.cert.Certificate; import java.security.cert.CertificateException; import javax.net.ssl.TrustManagerFactory; @@ -39,32 +40,44 @@ *

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. + * does custom hostname checking in accordance with these rules: + * + *

If the instance supports CAS certificates (instanceMetadata.casEnabled == true), or the + * connection is being made to a PSC endpoint (instanceMetadata.pscEnabled == true) the connector + * should validate that the server certificate subjectAlterantiveNames contains an entry that + * matches instanceMetadata.dnsName. + * + *

Otherwise, the connector should check that the Subject CN field contains the Cloud SQL + * instance ID in the form: "project-name:instance-name" */ class InstanceCheckingTrustManagerFactory extends TrustManagerFactory { - static InstanceCheckingTrustManagerFactory newInstance( - CloudSqlInstanceName instanceName, InstanceMetadata instanceMetadata) + static TrustManagerFactory newInstance(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.getInstanceCaCertificates().get(0)); - InstanceCheckingTrustManagerFactory tmf = - new InstanceCheckingTrustManagerFactory(instanceName, delegate); + // Add all the certificates in the chain of trust to the trust keystore. + for (Certificate cert : instanceMetadata.getInstanceCaCertificates()) { + trustedKeyStore.setCertificateEntry("ca" + cert.hashCode(), cert); + } + // Use a custom trust manager factory that checks the CN against the instance name + // The delegate TrustManagerFactory will check the certificate chain, but will not do + // hostname checking. + InstanceCheckingTrustManagerFactory tmf = + new InstanceCheckingTrustManagerFactory(instanceMetadata, delegate); tmf.init(trustedKeyStore); return tmf; } private InstanceCheckingTrustManagerFactory( - CloudSqlInstanceName instanceName, TrustManagerFactory delegate) { + InstanceMetadata instanceMetadata, TrustManagerFactory delegate) { super( - new InstanceCheckingTrustManagerFactorySpi(instanceName, delegate), + new InstanceCheckingTrustManagerFactorySpi(instanceMetadata, delegate), delegate.getProvider(), delegate.getAlgorithm()); } diff --git a/core/src/main/java/com/google/cloud/sql/core/InstanceCheckingTrustManagerFactorySpi.java b/core/src/main/java/com/google/cloud/sql/core/InstanceCheckingTrustManagerFactorySpi.java index 407de9c41..56ad62716 100644 --- a/core/src/main/java/com/google/cloud/sql/core/InstanceCheckingTrustManagerFactorySpi.java +++ b/core/src/main/java/com/google/cloud/sql/core/InstanceCheckingTrustManagerFactorySpi.java @@ -31,11 +31,11 @@ */ class InstanceCheckingTrustManagerFactorySpi extends TrustManagerFactorySpi { private final TrustManagerFactory delegate; - private final CloudSqlInstanceName instanceName; + private final InstanceMetadata instanceMetadata; InstanceCheckingTrustManagerFactorySpi( - CloudSqlInstanceName instanceName, TrustManagerFactory delegate) { - this.instanceName = instanceName; + InstanceMetadata instanceMetadata, TrustManagerFactory delegate) { + this.instanceMetadata = instanceMetadata; this.delegate = delegate; } @@ -65,7 +65,7 @@ protected TrustManager[] engineGetTrustManagers() { tm = new ConscryptWorkaroundDelegatingTrustManger(tm); } - delegates[i] = new InstanceCheckingTrustManger(instanceName, tm); + delegates[i] = new InstanceCheckingTrustManger(instanceMetadata, 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 index 69d41d8d2..c08edbab4 100644 --- a/core/src/main/java/com/google/cloud/sql/core/InstanceCheckingTrustManger.java +++ b/core/src/main/java/com/google/cloud/sql/core/InstanceCheckingTrustManger.java @@ -19,6 +19,9 @@ import java.net.Socket; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; import javax.naming.InvalidNameException; import javax.naming.ldap.LdapName; import javax.naming.ldap.Rdn; @@ -37,11 +40,11 @@ */ class InstanceCheckingTrustManger extends X509ExtendedTrustManager { private final X509ExtendedTrustManager tm; - private final CloudSqlInstanceName instanceName; + private final InstanceMetadata instanceMetadata; public InstanceCheckingTrustManger( - CloudSqlInstanceName instanceName, X509ExtendedTrustManager tm) { - this.instanceName = instanceName; + InstanceMetadata instanceMetadata, X509ExtendedTrustManager tm) { + this.instanceMetadata = instanceMetadata; this.tm = tm; } @@ -92,6 +95,66 @@ private void checkCertificateChain(X509Certificate[] chain) throws CertificateEx throw new CertificateException("Subject is missing"); } + if (instanceMetadata.isCasManagedCertificate() || instanceMetadata.isPscEnabled()) { + checkSan(chain); + } else { + checkCn(chain); + } + } + + private void checkSan(X509Certificate[] chain) throws CertificateException { + List sans = getSans(chain[0]); + String dns = instanceMetadata.getDnsName(); + if (dns == null || dns.isEmpty()) { + throw new CertificateException( + "Instance metadata for " + instanceMetadata.getInstanceName() + " has an empty dnsName"); + } + for (String san : sans) { + if (san.equalsIgnoreCase(dns)) { + return; + } + } + throw new CertificateException( + "Server certificate does not contain expected name '" + + instanceMetadata.getDnsName() + + "' for Cloud SQL instance " + + instanceMetadata.getInstanceName()); + } + + private List getSans(X509Certificate cert) throws CertificateException { + ArrayList names = new ArrayList<>(); + + Collection> sanAsn1Field = cert.getSubjectAlternativeNames(); + if (sanAsn1Field == null) { + return names; + } + + for (List item : sanAsn1Field) { + Integer type = (Integer) item.get(0); + // RFC 5280 section 4.2.1.6. "Subject Alternative Name" + // describes the structure of subjectAlternativeName record. + // type == 0 means this contains an "otherName" + // type == 2 means this contains a "dNSName" + if (type == 0 || type == 2) { + Object value = item.get(1); + if (value instanceof byte[]) { + // This would only happen if the customer provided a non-standard JSSE encryption + // provider. The standard JSSE providers all return a list of Strings for the SAN. + // To handle this case, the project would need to add the BouncyCastle crypto library + // as a dependency, and follow the example to decode an ASN1 SAN data structure: + // https://stackoverflow.com/questions/30993879/retrieve-subject-alternative-names-of-x-509-certificate-in-java + throw new UnsupportedOperationException( + "Server certificate SAN field cannot be decoded."); + } else if (value instanceof String) { + names.add((String) value); + } + } + } + return names; + } + + private void checkCn(X509Certificate[] chain) throws CertificateException { + String cn = null; try { @@ -111,7 +174,10 @@ private void checkCertificateChain(X509Certificate[] chain) throws CertificateEx } // parse CN from subject. CN always comes last in the list. - String instName = this.instanceName.getProjectId() + ":" + this.instanceName.getInstanceId(); + String instName = + this.instanceMetadata.getInstanceName().getProjectId() + + ":" + + this.instanceMetadata.getInstanceName().getInstanceId(); if (!instName.equals(cn)) { throw new CertificateException( "Server certificate CN does not match instance name. Server certificate CN=" 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 e4f2d2419..be56a4c94 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 @@ -133,19 +133,30 @@ public void setup() throws GeneralSecurityException { } MockHttpTransport fakeSuccessHttpTransport(Duration certDuration) { - return fakeSuccessHttpTransport(TestKeys.getServerCertPem(), certDuration, null); + return fakeSuccessHttpTransport(TestKeys.getServerCertPem(), certDuration, null, false, false); } MockHttpTransport fakeSuccessHttpTransport(Duration certDuration, String baseUrl) { - return fakeSuccessHttpTransport(TestKeys.getServerCertPem(), certDuration, baseUrl); + return fakeSuccessHttpTransport( + TestKeys.getServerCertPem(), certDuration, baseUrl, false, false); } MockHttpTransport fakeSuccessHttpTransport(String serverCert, Duration certDuration) { - return fakeSuccessHttpTransport(serverCert, certDuration, null); + return fakeSuccessHttpTransport(serverCert, certDuration, null, false, false); + } + + MockHttpTransport fakeSuccessHttpCasTransport(Duration certDuration) { + return fakeSuccessHttpTransport( + TestKeys.getCasServerCertChainPem(), certDuration, null, true, false); + } + + MockHttpTransport fakeSuccessHttpPscCasTransport(Duration certDuration) { + return fakeSuccessHttpTransport( + TestKeys.getCasServerCertChainPem(), certDuration, null, true, true); } MockHttpTransport fakeSuccessHttpTransport( - String serverCert, Duration certDuration, String baseUrl) { + String serverCert, Duration certDuration, String baseUrl, boolean cas, boolean psc) { final JsonFactory jsonFactory = new GsonFactory(); return new MockHttpTransport() { @Override @@ -167,7 +178,10 @@ public LowLevelHttpResponse execute() throws IOException { new IpMapping().setIpAddress(PRIVATE_IP).setType("PRIVATE"))) .setServerCaCert(new SslCert().setCert(serverCert)) .setDatabaseVersion("POSTGRES14") - .setRegion("myRegion"); + .setRegion("myRegion") + .setPscEnabled(psc ? Boolean.TRUE : null) + .setDnsName(cas || psc ? "db.example.com" : null) + .setServerCaMode(cas ? "GOOGLE_MANAGED_CAS_CA" : null); settings.setFactory(jsonFactory); response .setContent(settings.toPrettyString()) 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 3e8e6028c..80c9485d4 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 @@ -32,6 +32,8 @@ import java.net.Socket; import java.nio.file.Files; import java.nio.file.Path; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; import java.time.Duration; import java.time.Instant; import java.util.Collections; @@ -147,6 +149,39 @@ public void create_successfulPublicConnection() throws IOException, InterruptedE assertThat(readLine(socket)).isEqualTo(SERVER_MESSAGE); } + @Test + public void create_successfulPublicCasConnection() throws IOException, InterruptedException { + PrivateKey privateKey = TestKeys.getServerKeyPair().getPrivate(); + X509Certificate[] cert = TestKeys.getCasServerCertChain(); + + FakeSslServer sslServer = new FakeSslServer(privateKey, cert); + ConnectionConfig config = + new ConnectionConfig.Builder() + .withCloudSqlInstance("myProject:myRegion:myInstance") + .withIpTypes("PRIMARY") + .build(); + + int port = sslServer.start(PUBLIC_IP); + + ConnectionInfoRepositoryFactory factory = + new StubConnectionInfoRepositoryFactory(fakeSuccessHttpCasTransport(Duration.ZERO)); + + Connector connector = + new Connector( + config.getConnectorConfig(), + factory, + stubCredentialFactoryProvider.getInstanceCredentialFactory(config.getConnectorConfig()), + defaultExecutor, + clientKeyPair, + 10, + TEST_MAX_REFRESH_MS, + port); + + Socket socket = connector.connect(config, TEST_MAX_REFRESH_MS); + + assertThat(readLine(socket)).isEqualTo(SERVER_MESSAGE); + } + private boolean isWindows() { String os = System.getProperty("os.name").toLowerCase(); return os.contains("win"); 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 10a945b3e..a23598054 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 @@ -24,7 +24,6 @@ import java.security.KeyStore.PrivateKeyEntry; import java.security.PrivateKey; import java.security.SecureRandom; -import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; @@ -38,18 +37,23 @@ public class FakeSslServer { private final PrivateKey privateKey; - private final X509Certificate cert; + private final X509Certificate[] cert; FakeSslServer() { privateKey = TestKeys.getServerKeyPair().getPrivate(); - cert = TestKeys.getServerCert(); + cert = new X509Certificate[] {TestKeys.getServerCert()}; } - public FakeSslServer(PrivateKey privateKey, X509Certificate cert) { + public FakeSslServer(PrivateKey privateKey, X509Certificate[] cert) { this.privateKey = privateKey; this.cert = cert; } + public FakeSslServer(PrivateKey privateKey, X509Certificate cert) { + this.privateKey = privateKey; + this.cert = new X509Certificate[] {cert}; + } + int start(final String ip) throws InterruptedException { final CountDownLatch countDownLatch = new CountDownLatch(1); final AtomicInteger pickedPort = new AtomicInteger(); @@ -59,8 +63,7 @@ int start(final String ip) throws InterruptedException { try { KeyStore authKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()); authKeyStore.load(null, null); - PrivateKeyEntry serverCert = - new PrivateKeyEntry(privateKey, new Certificate[] {cert}); + PrivateKeyEntry serverCert = new PrivateKeyEntry(privateKey, cert); authKeyStore.setEntry( "serverCert", serverCert, new PasswordProtection(new char[0])); KeyManagerFactory keyManagerFactory = diff --git a/core/src/test/java/com/google/cloud/sql/core/MockAdminApi.java b/core/src/test/java/com/google/cloud/sql/core/MockAdminApi.java index e20524d74..b0b26c1bd 100644 --- a/core/src/test/java/com/google/cloud/sql/core/MockAdminApi.java +++ b/core/src/test/java/com/google/cloud/sql/core/MockAdminApi.java @@ -103,6 +103,7 @@ public void addConnectSettingsResponse( .setServerCaCert(new SslCert().setCert(TestKeys.getServerCertPem())) .setDatabaseVersion(databaseVersion) .setDnsName(pscHostname) + .setPscEnabled(pscHostname != null) .setRegion(cloudSqlInstanceName.getRegionId()); settings.setFactory(GsonFactory.getDefaultInstance()); 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 08cfcedd8..fc6a3f9f3 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 @@ -40,12 +40,11 @@ import java.util.List; import java.util.concurrent.ExecutionException; import javax.security.auth.x500.X500Principal; -import org.bouncycastle.asn1.ASN1Encodable; -import org.bouncycastle.asn1.DERSequence; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x509.BasicConstraints; import org.bouncycastle.asn1.x509.Extension; import org.bouncycastle.asn1.x509.GeneralName; +import org.bouncycastle.asn1.x509.GeneralNames; import org.bouncycastle.asn1.x509.KeyUsage; import org.bouncycastle.cert.CertIOException; import org.bouncycastle.cert.X509CertificateHolder; @@ -62,7 +61,9 @@ public class TestCertificateGenerator { public static final int DEFAULT_KEY_SIZE = 2048; private static final X500Name SERVER_CA_SUBJECT = - new X500Name("C=US,O=Google\\, Inc,CN=Google Cloud SQL Server CA"); + new X500Name("C=US,O=Google\\, Inc,CN=Google Cloud SQL Root CA"); + private static final X500Name SERVER_INTERMEDIATE_CA_SUBJECT = + new X500Name("C=US,O=Google\\, Inc,CN=Google Cloud SQL Intermediate CA"); private static final X500Name SIGNING_CA_SUBJECT = new X500Name("C=US,O=Google\\, Inc,CN=Google Cloud SQL Signing CA foo:baz"); @@ -74,12 +75,16 @@ public class TestCertificateGenerator { private final String SHA_256_WITH_RSA = "SHA256WithRSA"; private final KeyPair signingCaKeyPair; private final KeyPair serverCaKeyPair; + private final KeyPair serverIntermediateCaKeyPair; private final KeyPair domainServerKeyPair; private final KeyPair serverKeyPair; private final KeyPair clientKeyPair; private final X509Certificate signingCaCert; private final X509Certificate serverCaCert; + private final X509Certificate serverIntemediateCaCert; private final X509Certificate serverCertificate; + private final X509Certificate casServerCertificate; + private final X509Certificate[] casServerCertificateChain; private final X509Certificate domainServerCertificate; private final String PEM_HEADER = "-----BEGIN CERTIFICATE-----"; @@ -102,6 +107,7 @@ static KeyPair generateKeyPair() { TestCertificateGenerator() { this.serverCaKeyPair = generateKeyPair(); + this.serverIntermediateCaKeyPair = generateKeyPair(); this.signingCaKeyPair = generateKeyPair(); this.serverKeyPair = generateKeyPair(); this.clientKeyPair = generateKeyPair(); @@ -120,6 +126,29 @@ static KeyPair generateKeyPair() { ONE_YEAR_FROM_NOW, null); + this.serverIntemediateCaCert = + buildSignedCertificate( + SERVER_INTERMEDIATE_CA_SUBJECT, + serverIntermediateCaKeyPair.getPublic(), + SERVER_CA_SUBJECT, + serverCaKeyPair.getPrivate(), + ONE_YEAR_FROM_NOW, + null); + + this.casServerCertificate = + buildSignedCertificate( + SERVER_CERT_SUBJECT, + serverKeyPair.getPublic(), + SERVER_INTERMEDIATE_CA_SUBJECT, + serverIntermediateCaKeyPair.getPrivate(), + ONE_YEAR_FROM_NOW, + Collections.singletonList(new GeneralName(GeneralName.dNSName, "db.example.com"))); + + this.casServerCertificateChain = + new X509Certificate[] { + this.casServerCertificate, this.serverIntemediateCaCert, this.serverCaCert + }; + this.domainServerCertificate = buildSignedCertificate( DOMAIN_SERVER_CERT_SUBJECT, @@ -133,6 +162,14 @@ static KeyPair generateKeyPair() { } } + public X509Certificate getCasServerCertificate() { + return casServerCertificate; + } + + public X509Certificate[] getCasServerCertificateChain() { + return casServerCertificateChain; + } + public KeyPair getServerKeyPair() { return serverKeyPair; } @@ -270,11 +307,12 @@ private X509Certificate buildSignedCertificate( Extension.keyUsage, false, new KeyUsage(KeyUsage.cRLSign | KeyUsage.keyCertSign | KeyUsage.digitalSignature)); + if (subjectAlternateNames != null && !subjectAlternateNames.isEmpty()) { - ASN1Encodable[] names = - subjectAlternateNames.toArray(new ASN1Encodable[subjectAlternateNames.size()]); - certificateBuilder.addExtension( - Extension.subjectAlternativeName, false, new DERSequence(names)); + GeneralName[] gn = + subjectAlternateNames.toArray(new GeneralName[subjectAlternateNames.size()]); + GeneralNames subjectAltNames = new GeneralNames(gn); + certificateBuilder.addExtension(Extension.subjectAlternativeName, false, subjectAltNames); } X509CertificateHolder certificateHolder = certificateBuilder.build(csrContentSigner); 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 c8a1843cb..170fc95a9 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 @@ -78,4 +78,19 @@ public static X509Certificate getDomainServerCert() { public static String getDomainServerCertPem() { return certs.getPemForCert(certs.getDomainServerCertificate()); } + + public static X509Certificate[] getCasServerCertChain() { + return certs.getCasServerCertificateChain(); + } + + public static String getCasServerCertChainPem() { + StringBuilder s = new StringBuilder(); + for (X509Certificate c : certs.getCasServerCertificateChain()) { + if (s.length() > 0) { + s.append("\n"); + } + s.append(certs.getPemForCert(c)); + } + return s.toString(); + } }