diff --git a/oauth2_http/java/com/google/auth/mtls/MtlsUtils.java b/oauth2_http/java/com/google/auth/mtls/MtlsUtils.java new file mode 100644 index 000000000..401822505 --- /dev/null +++ b/oauth2_http/java/com/google/auth/mtls/MtlsUtils.java @@ -0,0 +1,135 @@ +/* + * Copyright 2026 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.auth.mtls; + +import com.google.api.core.InternalApi; +import com.google.auth.oauth2.EnvironmentProvider; +import com.google.auth.oauth2.PropertyProvider; +import com.google.common.base.Strings; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Locale; + +/** + * Utility class for mTLS related operations. + * + *
For internal use only. + */ +@InternalApi +public class MtlsUtils { + static final String CERTIFICATE_CONFIGURATION_ENV_VARIABLE = "GOOGLE_API_CERTIFICATE_CONFIG"; + static final String WELL_KNOWN_CERTIFICATE_CONFIG_FILE = "certificate_config.json"; + static final String CLOUDSDK_CONFIG_DIRECTORY = "gcloud"; + + private MtlsUtils() { + // Prevent instantiation for Utility class + } + + /** + * Returns the path to the client certificate file specified by the loaded workload certificate + * configuration. + * + * @return The path to the certificate file. + * @throws IOException if the certificate configuration cannot be found or loaded. + */ + public static String getCertificatePath( + EnvironmentProvider envProvider, PropertyProvider propProvider, String certConfigPathOverride) + throws IOException { + String certPath = + getWorkloadCertificateConfiguration(envProvider, propProvider, certConfigPathOverride) + .getCertPath(); + if (Strings.isNullOrEmpty(certPath)) { + throw new CertificateSourceUnavailableException( + "Certificate configuration loaded successfully, but does not contain a 'certificate_file' path."); + } + return certPath; + } + + /** + * Resolves and loads the workload certificate configuration. + * + *
The configuration file is resolved in the following order of precedence: 1. The provided + * certConfigPathOverride (if not null). 2. The path specified by the + * GOOGLE_API_CERTIFICATE_CONFIG environment variable. 3. The well-known certificate configuration + * file in the gcloud config directory. + * + * @param envProvider the environment provider to use for resolving environment variables + * @param propProvider the property provider to use for resolving system properties + * @param certConfigPathOverride optional override path for the configuration file + * @return the loaded WorkloadCertificateConfiguration + * @throws IOException if the configuration file cannot be found, read, or parsed + */ + static WorkloadCertificateConfiguration getWorkloadCertificateConfiguration( + EnvironmentProvider envProvider, PropertyProvider propProvider, String certConfigPathOverride) + throws IOException { + File certConfig; + if (certConfigPathOverride != null) { + certConfig = new File(certConfigPathOverride); + } else { + String envCredentialsPath = envProvider.getEnv(CERTIFICATE_CONFIGURATION_ENV_VARIABLE); + if (!Strings.isNullOrEmpty(envCredentialsPath)) { + certConfig = new File(envCredentialsPath); + } else { + certConfig = getWellKnownCertificateConfigFile(envProvider, propProvider); + } + } + + if (!certConfig.isFile()) { + throw new CertificateSourceUnavailableException( + "Certificate configuration file does not exist or is not a file: " + + certConfig.getAbsolutePath()); + } + try (InputStream certConfigStream = new FileInputStream(certConfig)) { + return WorkloadCertificateConfiguration.fromCertificateConfigurationStream(certConfigStream); + } + } + + private static File getWellKnownCertificateConfigFile( + EnvironmentProvider envProvider, PropertyProvider propProvider) { + File cloudConfigPath; + String envPath = envProvider.getEnv("CLOUDSDK_CONFIG"); + if (envPath != null) { + cloudConfigPath = new File(envPath); + } else { + String osName = propProvider.getProperty("os.name", "").toLowerCase(Locale.US); + if (osName.indexOf("windows") >= 0) { + File appDataPath = new File(envProvider.getEnv("APPDATA")); + cloudConfigPath = new File(appDataPath, CLOUDSDK_CONFIG_DIRECTORY); + } else { + File configPath = new File(propProvider.getProperty("user.home", ""), ".config"); + cloudConfigPath = new File(configPath, CLOUDSDK_CONFIG_DIRECTORY); + } + } + return new File(cloudConfigPath, WELL_KNOWN_CERTIFICATE_CONFIG_FILE); + } +} diff --git a/oauth2_http/java/com/google/auth/mtls/X509Provider.java b/oauth2_http/java/com/google/auth/mtls/X509Provider.java index 7ff490f0f..4127b1492 100644 --- a/oauth2_http/java/com/google/auth/mtls/X509Provider.java +++ b/oauth2_http/java/com/google/auth/mtls/X509Provider.java @@ -31,15 +31,17 @@ package com.google.auth.mtls; import com.google.api.client.util.SecurityUtils; -import com.google.common.base.Strings; +import com.google.api.core.InternalApi; +import com.google.auth.oauth2.EnvironmentProvider; +import com.google.auth.oauth2.PropertyProvider; +import com.google.auth.oauth2.SystemEnvironmentProvider; +import com.google.auth.oauth2.SystemPropertyProvider; import java.io.File; import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.SequenceInputStream; import java.security.KeyStore; -import java.util.Locale; /** * This class implements {@link MtlsProvider} for the Google Auth library transport layer via {@link @@ -47,11 +49,10 @@ * libraries, and the public facing methods may be changed without notice, and have no guarantee of * backwards compatibility. */ +@InternalApi public class X509Provider implements MtlsProvider { - static final String CERTIFICATE_CONFIGURATION_ENV_VARIABLE = "GOOGLE_API_CERTIFICATE_CONFIG"; - static final String WELL_KNOWN_CERTIFICATE_CONFIG_FILE = "certificate_config.json"; - static final String CLOUDSDK_CONFIG_DIRECTORY = "gcloud"; - + private final EnvironmentProvider envProvider; + private final PropertyProvider propProvider; private final String certConfigPathOverride; /** @@ -59,12 +60,32 @@ public class X509Provider implements MtlsProvider { * normal checks for the well known certificate configuration file path and environment variable. * This is meant for internal Google Cloud usage and behavior may be changed without warning. * + * @param envProvider environment provider used for environment variables + * @param propProvider property provider used for system properties * @param certConfigPathOverride the path to read the certificate configuration from. */ - public X509Provider(String certConfigPathOverride) { + @InternalApi + public X509Provider( + EnvironmentProvider envProvider, + PropertyProvider propProvider, + String certConfigPathOverride) { + this.envProvider = envProvider; + this.propProvider = propProvider; this.certConfigPathOverride = certConfigPathOverride; } + /** + * Creates an X509 provider with an override path for the certificate configuration. + * + * @param certConfigPathOverride the path to read the certificate configuration from. + */ + public X509Provider(String certConfigPathOverride) { + this( + SystemEnvironmentProvider.getInstance(), + SystemPropertyProvider.getInstance(), + certConfigPathOverride); + } + /** * Creates a new X.509 provider that will check the environment variable path and the well known * Gcloud certificate configuration location. This is meant for internal Google Cloud usage and @@ -74,29 +95,6 @@ public X509Provider() { this(null); } - /** - * Returns the path to the client certificate file specified by the loaded workload certificate - * configuration. - * - *
If the configuration has not been loaded yet (e.g., if {@link #getKeyStore()} has not been - * called), this method will attempt to load it first by searching the override path, environment - * variable, and well-known locations. - * - * @return The path to the certificate file. - * @throws IOException if the certificate configuration cannot be found or loaded, or if the - * configuration file does not specify a certificate path. - * @throws CertificateSourceUnavailableException if the configuration file is not found. - */ - public String getCertificatePath() throws IOException { - String certPath = getWorkloadCertificateConfiguration().getCertPath(); - if (Strings.isNullOrEmpty(certPath)) { - // Ensure the loaded configuration actually contains the required path. - throw new CertificateSourceUnavailableException( - "Certificate configuration loaded successfully, but does not contain a 'certificate_file' path."); - } - return certPath; - } - /** * Finds the certificate configuration file, then builds a Keystore using the X.509 certificate * and private key pointed to by the configuration. This will check the following locations in @@ -115,12 +113,14 @@ public String getCertificatePath() throws IOException { */ @Override public KeyStore getKeyStore() throws CertificateSourceUnavailableException, IOException { - WorkloadCertificateConfiguration workloadCertConfig = getWorkloadCertificateConfiguration(); + WorkloadCertificateConfiguration workloadCertConfig = + MtlsUtils.getWorkloadCertificateConfiguration( + envProvider, propProvider, certConfigPathOverride); // Read the certificate and private key file paths into streams. - try (InputStream certStream = createInputStream(new File(workloadCertConfig.getCertPath())); + try (InputStream certStream = new FileInputStream(new File(workloadCertConfig.getCertPath())); InputStream privateKeyStream = - createInputStream(new File(workloadCertConfig.getPrivateKeyPath())); + new FileInputStream(new File(workloadCertConfig.getPrivateKeyPath())); SequenceInputStream certAndPrivateKeyStream = new SequenceInputStream(certStream, privateKeyStream)) { @@ -149,74 +149,4 @@ public boolean isAvailable() throws IOException { } return true; } - - private WorkloadCertificateConfiguration getWorkloadCertificateConfiguration() - throws IOException { - File certConfig; - if (this.certConfigPathOverride != null) { - certConfig = new File(certConfigPathOverride); - } else { - String envCredentialsPath = getEnv(CERTIFICATE_CONFIGURATION_ENV_VARIABLE); - if (!Strings.isNullOrEmpty(envCredentialsPath)) { - certConfig = new File(envCredentialsPath); - } else { - certConfig = getWellKnownCertificateConfigFile(); - } - } - InputStream certConfigStream = null; - try { - if (!isFile(certConfig)) { - // Path will be put in the message from the catch block below - throw new CertificateSourceUnavailableException("File does not exist."); - } - certConfigStream = createInputStream(certConfig); - return WorkloadCertificateConfiguration.fromCertificateConfigurationStream(certConfigStream); - } finally { - if (certConfigStream != null) { - certConfigStream.close(); - } - } - } - - /* - * Start of methods to allow overriding in the test code to isolate from the environment. - */ - boolean isFile(File file) { - return file.isFile(); - } - - InputStream createInputStream(File file) throws FileNotFoundException { - return new FileInputStream(file); - } - - String getEnv(String name) { - return System.getenv(name); - } - - String getOsName() { - return getProperty("os.name", "").toLowerCase(Locale.US); - } - - String getProperty(String property, String def) { - return System.getProperty(property, def); - } - - /* - * End of methods to allow overriding in the test code to isolate from the environment. - */ - - private File getWellKnownCertificateConfigFile() { - File cloudConfigPath; - String envPath = getEnv("CLOUDSDK_CONFIG"); - if (envPath != null) { - cloudConfigPath = new File(envPath); - } else if (getOsName().indexOf("windows") >= 0) { - File appDataPath = new File(getEnv("APPDATA")); - cloudConfigPath = new File(appDataPath, CLOUDSDK_CONFIG_DIRECTORY); - } else { - File configPath = new File(getProperty("user.home", ""), ".config"); - cloudConfigPath = new File(configPath, CLOUDSDK_CONFIG_DIRECTORY); - } - return new File(cloudConfigPath, WELL_KNOWN_CERTIFICATE_CONFIG_FILE); - } } diff --git a/oauth2_http/java/com/google/auth/oauth2/AwsCredentials.java b/oauth2_http/java/com/google/auth/oauth2/AwsCredentials.java index 308576531..bb943fcdf 100644 --- a/oauth2_http/java/com/google/auth/oauth2/AwsCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/AwsCredentials.java @@ -196,11 +196,6 @@ String getRegionalCredentialVerificationUrl() { return this.regionalCredentialVerificationUrl; } - @VisibleForTesting - String getEnv(String name) { - return System.getenv(name); - } - @VisibleForTesting AwsSecurityCredentialsSupplier getAwsSecurityCredentialsSupplier() { return this.awsSecurityCredentialsSupplier; diff --git a/oauth2_http/java/com/google/auth/oauth2/EnvironmentProvider.java b/oauth2_http/java/com/google/auth/oauth2/EnvironmentProvider.java index 5c77ecc65..f4a059117 100644 --- a/oauth2_http/java/com/google/auth/oauth2/EnvironmentProvider.java +++ b/oauth2_http/java/com/google/auth/oauth2/EnvironmentProvider.java @@ -1,6 +1,13 @@ package com.google.auth.oauth2; -/** Interface for an environment provider. */ -interface EnvironmentProvider { +import com.google.api.core.InternalApi; + +/** + * Interface for an environment provider. + * + *
For internal use only.
+ */
+@InternalApi
+public interface EnvironmentProvider {
String getEnv(String name);
}
diff --git a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java
index 7f9f0c207..e92c64bed 100644
--- a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java
+++ b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java
@@ -97,6 +97,7 @@ public abstract class ExternalAccountCredentials extends GoogleCredentials {
@Nullable protected ImpersonatedCredentials impersonatedCredentials;
private EnvironmentProvider environmentProvider;
+ private PropertyProvider propertyProvider;
private int connectTimeout;
private int readTimeout;
@@ -205,6 +206,7 @@ protected ExternalAccountCredentials(
: scopes;
this.environmentProvider =
environmentProvider == null ? SystemEnvironmentProvider.getInstance() : environmentProvider;
+ this.propertyProvider = SystemPropertyProvider.getInstance();
this.workforcePoolUserProject = null;
this.serviceAccountImpersonationOptions =
new ServiceAccountImpersonationOptions(new HashMap For internal use only.
+ */
+@InternalApi
+public interface PropertyProvider {
+ String getProperty(String property, String def);
+}
diff --git a/oauth2_http/java/com/google/auth/oauth2/SystemEnvironmentProvider.java b/oauth2_http/java/com/google/auth/oauth2/SystemEnvironmentProvider.java
index a29707721..a58285831 100644
--- a/oauth2_http/java/com/google/auth/oauth2/SystemEnvironmentProvider.java
+++ b/oauth2_http/java/com/google/auth/oauth2/SystemEnvironmentProvider.java
@@ -1,9 +1,45 @@
+/*
+ * Copyright 2026 Google LLC
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
package com.google.auth.oauth2;
+import com.google.api.core.InternalApi;
import java.io.Serializable;
-/** Represents the default system environment provider. */
-class SystemEnvironmentProvider implements EnvironmentProvider, Serializable {
+/**
+ * Represents the default system environment provider.
+ *
+ * For internal use only.
+ */
+@InternalApi
+public class SystemEnvironmentProvider implements EnvironmentProvider, Serializable {
static final SystemEnvironmentProvider INSTANCE = new SystemEnvironmentProvider();
private static final long serialVersionUID = -4698164985883575244L;
diff --git a/oauth2_http/java/com/google/auth/oauth2/SystemPropertyProvider.java b/oauth2_http/java/com/google/auth/oauth2/SystemPropertyProvider.java
new file mode 100644
index 000000000..f7cff5de5
--- /dev/null
+++ b/oauth2_http/java/com/google/auth/oauth2/SystemPropertyProvider.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2025 Google LLC
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.google.auth.oauth2;
+
+import com.google.api.core.InternalApi;
+import java.io.Serializable;
+
+/**
+ * Represents the default system property provider.
+ *
+ * For internal use only.
+ */
+@InternalApi
+public class SystemPropertyProvider implements PropertyProvider, Serializable {
+ public static final SystemPropertyProvider INSTANCE = new SystemPropertyProvider();
+ private static final long serialVersionUID = 1L;
+
+ private SystemPropertyProvider() {}
+
+ @Override
+ public String getProperty(String property, String def) {
+ return System.getProperty(property, def);
+ }
+
+ public static SystemPropertyProvider getInstance() {
+ return INSTANCE;
+ }
+}
diff --git a/oauth2_http/javatests/com/google/auth/mtls/X509ProviderTest.java b/oauth2_http/javatests/com/google/auth/mtls/X509ProviderTest.java
index 7f3927654..88037c2c2 100644
--- a/oauth2_http/javatests/com/google/auth/mtls/X509ProviderTest.java
+++ b/oauth2_http/javatests/com/google/auth/mtls/X509ProviderTest.java
@@ -36,73 +36,44 @@
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
-import java.io.ByteArrayInputStream;
+import com.google.auth.oauth2.SystemPropertyProvider;
+import com.google.auth.oauth2.TestEnvironmentProvider;
+import com.google.auth.oauth2.TestPropertyProvider;
import java.io.File;
-import java.io.FileNotFoundException;
+import java.io.FileInputStream;
import java.io.IOException;
-import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
-import java.util.HashMap;
-import java.util.Map;
import org.junit.jupiter.api.Test;
class X509ProviderTest {
- private static final String TEST_CERT =
- "-----BEGIN CERTIFICATE-----\n"
- + "MIICGzCCAYSgAwIBAgIIWrt6xtmHPs4wDQYJKoZIhvcNAQEFBQAwMzExMC8GA1UE\n"
- + "AxMoMTAwOTEyMDcyNjg3OC5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbTAeFw0x\n"
- + "MjEyMDExNjEwNDRaFw0yMjExMjkxNjEwNDRaMDMxMTAvBgNVBAMTKDEwMDkxMjA3\n"
- + "MjY4NzguYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20wgZ8wDQYJKoZIhvcNAQEB\n"
- + "BQADgY0AMIGJAoGBAL1SdY8jTUVU7O4/XrZLYTw0ON1lV6MQRGajFDFCqD2Fd9tQ\n"
- + "GLW8Iftx9wfXe1zuaehJSgLcyCxazfyJoN3RiONBihBqWY6d3lQKqkgsRTNZkdFJ\n"
- + "Wdzl/6CxhK9sojh2p0r3tydtv9iwq5fuuWIvtODtT98EgphhncQAqkKoF3zVAgMB\n"
- + "AAGjODA2MAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgeAMBYGA1UdJQEB/wQM\n"
- + "MAoGCCsGAQUFBwMCMA0GCSqGSIb3DQEBBQUAA4GBAD8XQEqzGePa9VrvtEGpf+R4\n"
- + "fkxKbcYAzqYq202nKu0kfjhIYkYSBj6gi348YaxE64yu60TVl42l5HThmswUheW4\n"
- + "uQIaq36JvwvsDP5Zoj5BgiNSnDAFQp+jJFBRUA5vooJKgKgMDf/r/DCOsbO6VJF1\n"
- + "kWwa9n19NFiV0z3m6isj\n"
- + "-----END CERTIFICATE-----\n";
-
- private static final String TEST_PRIVATE_KEY =
- "-----BEGIN PRIVATE KEY-----\n"
- + "MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAL1SdY8jTUVU7O4/\n"
- + "XrZLYTw0ON1lV6MQRGajFDFCqD2Fd9tQGLW8Iftx9wfXe1zuaehJSgLcyCxazfyJ\n"
- + "oN3RiONBihBqWY6d3lQKqkgsRTNZkdFJWdzl/6CxhK9sojh2p0r3tydtv9iwq5fu\n"
- + "uWIvtODtT98EgphhncQAqkKoF3zVAgMBAAECgYB51B9cXe4yiGTzJ4pOKpHGySAy\n"
- + "sC1F/IjXt2eeD3PuKv4m/hL4l7kScpLx0+NJuQ4j8U2UK/kQOdrGANapB1ZbMZAK\n"
- + "/q0xmIUzdNIDiGSoTXGN2mEfdsEpQ/Xiv0lyhYBBPC/K4sYIpHccnhSRQUZlWLLY\n"
- + "lE5cFNKC9b7226mNvQJBAPt0hfCNIN0kUYOA9jdLtx7CE4ySGMPf5KPBuzPd8ty1\n"
- + "fxaFm9PB7B76VZQYmHcWy8rT5XjoLJHrmGW1ZvP+iDsCQQDAvnKoarPOGb5iJfkq\n"
- + "RrA4flf1TOlf+1+uqIOJ94959jkkJeb0gv/TshDnm6/bWn+1kJylQaKygCizwPwB\n"
- + "Z84vAkA0Duur4YvsPJijoQ9YY1SGCagCcjyuUKwFOxaGpmyhRPIKt56LOJqpzyno\n"
- + "fy8ReKa4VyYq4eZYT249oFCwMwIBAkAROPNF2UL3x5UbcAkznd1hLujtIlI4IV4L\n"
- + "XUNjsJtBap7we/KHJq11XRPlniO4lf2TW7iji5neGVWJulTKS1xBAkAerktk4Hsw\n"
- + "ErUaUG1s/d+Sgc8e/KMeBElV+NxGhcWEeZtfHMn/6VOlbzY82JyvC9OKC80A5CAE\n"
- + "VUV6b25kqrcu\n"
- + "-----END PRIVATE KEY-----";
+ private static final String TEST_CERT_PATH = "testresources/mtls/test_cert.pem";
+ private static final String TEST_CONFIG_PATH = "testresources/mtls/certificate_config.json";
@Test
void x509Provider_fileDoesntExist_throws() {
String certConfigPath = "badfile.txt";
- X509Provider testProvider = new TestX509Provider(certConfigPath);
- String expectedErrorMessage = "File does not exist.";
-
+ X509Provider testProvider = new X509Provider(certConfigPath);
+ String expectedErrorMessage =
+ "Certificate configuration file does not exist or is not a file: "
+ + new File(certConfigPath).getAbsolutePath();
CertificateSourceUnavailableException exception =
assertThrows(CertificateSourceUnavailableException.class, testProvider::getKeyStore);
- assertTrue(exception.getMessage().contains(expectedErrorMessage));
+ assertEquals(expectedErrorMessage, exception.getMessage());
}
@Test
- void x509Provider_emptyFile_throws() {
- String certConfigPath = "certConfig.txt";
- InputStream certConfigStream = new ByteArrayInputStream("".getBytes());
- TestX509Provider testProvider = new TestX509Provider(certConfigPath);
- testProvider.addFile(certConfigPath, certConfigStream);
+ void x509Provider_emptyFile_throws() throws IOException {
+ Path emptyConfig = Files.createTempFile("emptyConfig", ".txt");
+ emptyConfig.toFile().deleteOnExit();
+
+ X509Provider testProvider = new X509Provider(emptyConfig.toString());
String expectedErrorMessage = "no JSON input found";
IllegalArgumentException exception =
@@ -112,21 +83,13 @@ void x509Provider_emptyFile_throws() {
@Test
void x509Provider_succeeds() throws IOException, KeyStoreException, CertificateException {
- String certConfigPath = "certConfig.txt";
- String certPath = "cert.crt";
- String keyPath = "key.crt";
- InputStream certConfigStream =
- WorkloadCertificateConfigurationTest.writeWorkloadCertificateConfigStream(
- certPath, keyPath);
-
- TestX509Provider testProvider = new TestX509Provider(certConfigPath);
- testProvider.addFile(certConfigPath, certConfigStream);
- testProvider.addFile(certPath, new ByteArrayInputStream(TEST_CERT.getBytes()));
- testProvider.addFile(keyPath, new ByteArrayInputStream(TEST_PRIVATE_KEY.getBytes()));
+ X509Provider testProvider = new X509Provider(TEST_CONFIG_PATH);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
- Certificate expectedCert =
- cf.generateCertificate(new ByteArrayInputStream(TEST_CERT.getBytes()));
+ Certificate expectedCert;
+ try (FileInputStream fis = new FileInputStream(new File(TEST_CERT_PATH))) {
+ expectedCert = cf.generateCertificate(fis);
+ }
// Assert that the store has the expected certificate and only the expected certificate.
KeyStore store = testProvider.getKeyStore();
@@ -137,22 +100,17 @@ void x509Provider_succeeds() throws IOException, KeyStoreException, CertificateE
@Test
void x509Provider_succeeds_withEnvVariable()
throws IOException, KeyStoreException, CertificateException {
- String certConfigPath = "certConfig.txt";
- String certPath = "cert.crt";
- String keyPath = "key.crt";
- InputStream certConfigStream =
- WorkloadCertificateConfigurationTest.writeWorkloadCertificateConfigStream(
- certPath, keyPath);
-
- TestX509Provider testProvider = new TestX509Provider();
- testProvider.setEnv("GOOGLE_API_CERTIFICATE_CONFIG", certConfigPath);
- testProvider.addFile(certConfigPath, certConfigStream);
- testProvider.addFile(certPath, new ByteArrayInputStream(TEST_CERT.getBytes()));
- testProvider.addFile(keyPath, new ByteArrayInputStream(TEST_PRIVATE_KEY.getBytes()));
+ TestEnvironmentProvider envProvider = new TestEnvironmentProvider();
+ envProvider.setEnv("GOOGLE_API_CERTIFICATE_CONFIG", TEST_CONFIG_PATH);
+
+ X509Provider testProvider =
+ new X509Provider(envProvider, SystemPropertyProvider.getInstance(), null);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
- Certificate expectedCert =
- cf.generateCertificate(new ByteArrayInputStream(TEST_CERT.getBytes()));
+ Certificate expectedCert;
+ try (FileInputStream fis = new FileInputStream(new File(TEST_CERT_PATH))) {
+ expectedCert = cf.generateCertificate(fis);
+ }
// Assert that the store has the expected certificate and only the expected certificate.
KeyStore store = testProvider.getKeyStore();
@@ -163,22 +121,17 @@ void x509Provider_succeeds_withEnvVariable()
@Test
void x509Provider_succeeds_withWellKnownPath()
throws IOException, KeyStoreException, CertificateException {
- String certConfigPath = "certConfig.txt";
- String certPath = "cert.crt";
- String keyPath = "key.crt";
- InputStream certConfigStream =
- WorkloadCertificateConfigurationTest.writeWorkloadCertificateConfigStream(
- certPath, keyPath);
-
- TestX509Provider testProvider = new TestX509Provider();
- testProvider.setEnv("GOOGLE_API_CERTIFICATE_CONFIG", certConfigPath);
- testProvider.addFile(certConfigPath, certConfigStream);
- testProvider.addFile(certPath, new ByteArrayInputStream(TEST_CERT.getBytes()));
- testProvider.addFile(keyPath, new ByteArrayInputStream(TEST_PRIVATE_KEY.getBytes()));
+ TestEnvironmentProvider envProvider = new TestEnvironmentProvider();
+ envProvider.setEnv("CLOUDSDK_CONFIG", "testresources/mtls/");
+
+ X509Provider testProvider =
+ new X509Provider(envProvider, SystemPropertyProvider.getInstance(), null);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
- Certificate expectedCert =
- cf.generateCertificate(new ByteArrayInputStream(TEST_CERT.getBytes()));
+ Certificate expectedCert;
+ try (FileInputStream fis = new FileInputStream(new File(TEST_CERT_PATH))) {
+ expectedCert = cf.generateCertificate(fis);
+ }
// Assert that the store has the expected certificate and only the expected certificate.
KeyStore store = testProvider.getKeyStore();
@@ -186,53 +139,34 @@ void x509Provider_succeeds_withWellKnownPath()
assertNotNull(store.getCertificateAlias(expectedCert));
}
- static class TestX509Provider extends X509Provider {
- private final Map