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()); @@ -253,6 +255,10 @@ protected ExternalAccountCredentials(ExternalAccountCredentials.Builder builder) builder.environmentProvider == null ? SystemEnvironmentProvider.getInstance() : builder.environmentProvider; + this.propertyProvider = + builder.propertyProvider == null + ? SystemPropertyProvider.getInstance() + : builder.propertyProvider; this.serviceAccountImpersonationOptions = builder.serviceAccountImpersonationOptions == null ? new ServiceAccountImpersonationOptions(new HashMap()) @@ -657,6 +663,10 @@ EnvironmentProvider getEnvironmentProvider() { return environmentProvider; } + PropertyProvider getPropertyProvider() { + return propertyProvider; + } + /** * @return whether the current configuration is for Workforce Pools (which enable 3p user * identities, rather than workloads) @@ -772,6 +782,7 @@ public abstract static class Builder extends GoogleCredentials.Builder { protected String tokenInfoUrl; protected CredentialSource credentialSource; protected EnvironmentProvider environmentProvider; + protected PropertyProvider propertyProvider; protected HttpTransportFactory transportFactory; @Nullable protected String serviceAccountImpersonationUrl; @@ -806,6 +817,7 @@ protected Builder(ExternalAccountCredentials credentials) { this.clientSecret = credentials.clientSecret; this.scopes = credentials.scopes; this.environmentProvider = credentials.environmentProvider; + this.propertyProvider = credentials.propertyProvider; this.workforcePoolUserProject = credentials.workforcePoolUserProject; this.serviceAccountImpersonationOptions = credentials.serviceAccountImpersonationOptions; this.metricsHandler = credentials.metricsHandler; @@ -1029,6 +1041,18 @@ Builder setEnvironmentProvider(EnvironmentProvider environmentProvider) { return this; } + /** + * Sets the optional Property Provider. + * + * @param propertyProvider the {@code PropertyProvider} to set + * @return this {@code Builder} object + */ + @CanIgnoreReturnValue + Builder setPropertyProvider(PropertyProvider propertyProvider) { + this.propertyProvider = propertyProvider; + return this; + } + @Override public abstract ExternalAccountCredentials build(); } diff --git a/oauth2_http/java/com/google/auth/oauth2/IdentityPoolCredentials.java b/oauth2_http/java/com/google/auth/oauth2/IdentityPoolCredentials.java index ee5df1466..5a7a2657c 100644 --- a/oauth2_http/java/com/google/auth/oauth2/IdentityPoolCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/IdentityPoolCredentials.java @@ -33,6 +33,7 @@ import com.google.auth.http.HttpTransportFactory; import com.google.auth.mtls.MtlsHttpTransportFactory; +import com.google.auth.mtls.MtlsUtils; import com.google.auth.mtls.X509Provider; import com.google.auth.oauth2.IdentityPoolCredentialSource.IdentityPoolCredentialSourceType; import com.google.common.annotations.VisibleForTesting; @@ -166,7 +167,10 @@ private IdentityPoolSubjectTokenSupplier createCertificateSubjectTokenSupplier( this.transportFactory = new MtlsHttpTransportFactory(mtlsKeyStore); // Initialize the subject token supplier with the certificate path. - credentialSource.setCredentialLocation(x509Provider.getCertificatePath()); + String explicitCertConfigPath = getExplicitCertConfigPath(credentialSource); + credentialSource.setCredentialLocation( + MtlsUtils.getCertificatePath( + getEnvironmentProvider(), getPropertyProvider(), explicitCertConfigPath)); return new CertificateIdentityPoolSubjectTokenSupplier(credentialSource); } @@ -179,15 +183,21 @@ private X509Provider getX509Provider( X509Provider x509Provider = builder.x509Provider; if (x509Provider == null) { // Determine the certificate path based on the configuration. - String explicitCertConfigPath = - certConfig.useDefaultCertificateConfig() - ? null - : certConfig.getCertificateConfigLocation(); - x509Provider = new X509Provider(explicitCertConfigPath); + String explicitCertConfigPath = getExplicitCertConfigPath(credentialSource); + x509Provider = + new X509Provider(getEnvironmentProvider(), getPropertyProvider(), explicitCertConfigPath); } return x509Provider; } + private static String getExplicitCertConfigPath(IdentityPoolCredentialSource credentialSource) { + IdentityPoolCredentialSource.CertificateConfig certConfig = + credentialSource.getCertificateConfig(); + return certConfig.useDefaultCertificateConfig() + ? null + : certConfig.getCertificateConfigLocation(); + } + public static class Builder extends ExternalAccountCredentials.Builder { private IdentityPoolSubjectTokenSupplier subjectTokenSupplier; @@ -212,6 +222,7 @@ public static class Builder extends ExternalAccountCredentials.Builder { * @return this {@code Builder} object */ @CanIgnoreReturnValue + @VisibleForTesting Builder setX509Provider(X509Provider x509Provider) { this.x509Provider = x509Provider; return this; diff --git a/oauth2_http/java/com/google/auth/oauth2/PropertyProvider.java b/oauth2_http/java/com/google/auth/oauth2/PropertyProvider.java new file mode 100644 index 000000000..fa4d2db07 --- /dev/null +++ b/oauth2_http/java/com/google/auth/oauth2/PropertyProvider.java @@ -0,0 +1,43 @@ +/* + * 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; + +/** + * Interface for a system property provider. + * + *

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 files; - private final Map variables; - private final Map properties; - - TestX509Provider() { - this(null); - } - - TestX509Provider(String filePathOverride) { - super(filePathOverride); - this.files = new HashMap<>(); - this.variables = new HashMap<>(); - this.properties = new HashMap<>(); - } + @Test + void x509Provider_succeeds_withWindowsPath() + throws IOException, KeyStoreException, CertificateException { + Path windowsTempDir = Files.createTempDirectory("windowsTempDir"); + windowsTempDir.toFile().deleteOnExit(); + Path gcloudDir = windowsTempDir.resolve("gcloud"); + Files.createDirectory(gcloudDir); + Path configPath = gcloudDir.resolve("certificate_config.json"); - void addFile(String file, InputStream stream) { - files.put(file, stream); - } + // Copy the valid config to this new temp location + Files.copy(new File(TEST_CONFIG_PATH).toPath(), configPath); - @Override - String getEnv(String name) { - return variables.get(name); - } + TestEnvironmentProvider envProvider = new TestEnvironmentProvider(); + envProvider.setEnv("APPDATA", windowsTempDir.toString()); - void setEnv(String name, String value) { - variables.put(name, value); - } + TestPropertyProvider propProvider = new TestPropertyProvider(); + propProvider.setProperty("os.name", "Windows 10"); - @Override - String getProperty(String property, String def) { - String value = properties.get(property); - return value == null ? def : value; - } + X509Provider testProvider = new X509Provider(envProvider, propProvider, null); - @Override - boolean isFile(File file) { - return files.containsKey(file.getPath()); + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + Certificate expectedCert; + try (FileInputStream fis = new FileInputStream(new File(TEST_CERT_PATH))) { + expectedCert = cf.generateCertificate(fis); } - @Override - InputStream createInputStream(File file) throws FileNotFoundException { - InputStream stream = files.get(file.getPath()); - if (stream == null) { - throw new FileNotFoundException(file.getPath()); - } - return stream; - } + KeyStore store = testProvider.getKeyStore(); + assertEquals(1, store.size()); + assertNotNull(store.getCertificateAlias(expectedCert)); } } diff --git a/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java index 6997c79b0..323ed1a8f 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java @@ -40,8 +40,6 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; import com.google.api.client.http.HttpTransport; import com.google.api.client.json.GenericJson; @@ -1045,14 +1043,42 @@ void serialize() throws IOException, ClassNotFoundException { } @Test - void build_withCertificateSourceAndCustomX509Provider_success() - throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException { - // Create an empty KeyStore and a spy on a custom X509Provider. - KeyStore keyStore = KeyStore.getInstance("JKS"); - keyStore.load(null, null); - TestX509Provider x509Provider = - spy(new TestX509Provider(keyStore, "/path/to/certificate.json")); + void build_withCertificateSource_succeeds() throws Exception { + // Set up credential source for certificate type. + Map certificateMap = new HashMap<>(); + certificateMap.put("use_default_certificate_config", false); + certificateMap.put("certificate_config_location", "testresources/mtls/certificate_config.json"); + Map credentialSourceMap = new HashMap<>(); + credentialSourceMap.put("certificate", certificateMap); + IdentityPoolCredentialSource credentialSource = + new IdentityPoolCredentialSource(credentialSourceMap); + MockExternalAccountCredentialsTransportFactory mockTransportFactory = + new MockExternalAccountCredentialsTransportFactory(); + + // Build credentials. + IdentityPoolCredentials credentials = + IdentityPoolCredentials.newBuilder() + .setHttpTransportFactory(mockTransportFactory) + .setAudience("test-audience") + .setSubjectTokenType("test-token-type") + .setCredentialSource(credentialSource) + .build(); + + // Verify successful creation and correct internal setup. + assertNotNull(credentials, "Credentials should be successfully created"); + assertTrue( + credentials.getIdentityPoolSubjectTokenSupplier() + instanceof CertificateIdentityPoolSubjectTokenSupplier, + "Subject token supplier should be for certificates"); + assertEquals( + IdentityPoolCredentials.CERTIFICATE_METRICS_HEADER_VALUE, + credentials.getCredentialSourceType(), + "Metrics header should indicate certificate source"); + } + @Test + void build_withDefaultCertificateConfig_success() + throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException { // Set up credential source for certificate type. Map certificateMap = new HashMap<>(); certificateMap.put("use_default_certificate_config", true); @@ -1063,11 +1089,18 @@ void build_withCertificateSourceAndCustomX509Provider_success() MockExternalAccountCredentialsTransportFactory mockTransportFactory = new MockExternalAccountCredentialsTransportFactory(); - // Build credentials with the custom provider. + // Use the pre-existing test configuration file to bypass well-known path resolution. + EnvironmentProvider mockEnvProvider = + name -> + "GOOGLE_API_CERTIFICATE_CONFIG".equals(name) + ? new File("testresources/mtls/certificate_config.json").getAbsolutePath() + : null; + + // Build credentials using the default provider (no setX509Provider). IdentityPoolCredentials credentials = IdentityPoolCredentials.newBuilder() - .setX509Provider(x509Provider) .setHttpTransportFactory(mockTransportFactory) + .setEnvironmentProvider(mockEnvProvider) .setAudience("test-audience") .setSubjectTokenType("test-token-type") .setCredentialSource(credentialSource) @@ -1083,10 +1116,6 @@ void build_withCertificateSourceAndCustomX509Provider_success() IdentityPoolCredentials.CERTIFICATE_METRICS_HEADER_VALUE, credentials.getCredentialSourceType(), "Metrics header should indicate certificate source"); - - // Verify the custom provider methods were called during build. - verify(x509Provider).getKeyStore(); - verify(x509Provider).getCertificatePath(); } @Test @@ -1148,15 +1177,17 @@ void build_withCustomProvider_throwsOnGetKeyStore() @Test void build_withCustomProvider_throwsOnGetCertificatePath() throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException { - // Simulate a scenario where the X509Provider cannot access or read the certificate - // configuration file needed to determine the certificate path, resulting in an IOException. + // Simulate a scenario where path resolution fails during build with a custom + // provider. + // We achieve this by passing a non-existent configuration path which causes + // MtlsUtils to throw + // IOException. KeyStore keyStore = KeyStore.getInstance("JKS"); keyStore.load(null, null); TestX509Provider x509Provider = new TestX509Provider(keyStore, "/path/to/certificate.json"); - x509Provider.setShouldThrowOnGetCertificatePath(true); // Configure to throw Map certificateMap = new HashMap<>(); - certificateMap.put("certificate_config_location", "/path/to/certificate.json"); + certificateMap.put("certificate_config_location", "/non/existent/path.json"); // Expect RuntimeException because the constructor wraps the IOException. RuntimeException exception = @@ -1164,10 +1195,9 @@ void build_withCustomProvider_throwsOnGetCertificatePath() RuntimeException.class, () -> createCredentialsWithCertificate(x509Provider, certificateMap)); - // Verify the cause is the expected IOException from the mock. + // Verify the cause is the expected IOException (or subclass) from MtlsUtils. assertNotNull(exception.getCause()); assertTrue(exception.getCause() instanceof IOException); - assertEquals("Simulated IOException on certificate path", exception.getCause().getMessage()); // Verify the wrapper exception message assertEquals( @@ -1276,7 +1306,6 @@ private static class TestX509Provider extends X509Provider { private final KeyStore keyStore; private final String certificatePath; private boolean shouldThrowOnGetKeyStore = false; - private boolean shouldThrowOnGetCertificatePath = false; TestX509Provider(KeyStore keyStore, String certificatePath) { super(); @@ -1292,20 +1321,8 @@ public KeyStore getKeyStore() throws IOException { return keyStore; } - @Override - public String getCertificatePath() throws IOException { - if (shouldThrowOnGetCertificatePath) { - throw new IOException("Simulated IOException on certificate path"); - } - return certificatePath; - } - void setShouldThrowOnGetKeyStore(boolean shouldThrow) { this.shouldThrowOnGetKeyStore = shouldThrow; } - - void setShouldThrowOnGetCertificatePath(boolean shouldThrow) { - this.shouldThrowOnGetCertificatePath = shouldThrow; - } } } diff --git a/oauth2_http/javatests/com/google/auth/oauth2/TestEnvironmentProvider.java b/oauth2_http/javatests/com/google/auth/oauth2/TestEnvironmentProvider.java index 9e6de1108..6c0c85766 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/TestEnvironmentProvider.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/TestEnvironmentProvider.java @@ -3,7 +3,7 @@ import java.util.HashMap; import java.util.Map; -final class TestEnvironmentProvider implements EnvironmentProvider { +public final class TestEnvironmentProvider implements EnvironmentProvider { private final Map environmentVariables = new HashMap<>(); diff --git a/oauth2_http/javatests/com/google/auth/oauth2/TestPropertyProvider.java b/oauth2_http/javatests/com/google/auth/oauth2/TestPropertyProvider.java new file mode 100644 index 000000000..b146874d1 --- /dev/null +++ b/oauth2_http/javatests/com/google/auth/oauth2/TestPropertyProvider.java @@ -0,0 +1,52 @@ +/* + * 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 java.util.HashMap; +import java.util.Map; + +public class TestPropertyProvider implements PropertyProvider { + private final Map properties = new HashMap<>(); + + public TestPropertyProvider() {} + + public void setProperty(String property, String value) { + properties.put(property, value); + } + + @Override + public String getProperty(String property, String def) { + if (properties.containsKey(property)) { + return properties.get(property); + } + return def; + } +} diff --git a/oauth2_http/testresources/mtls/certificate_config.json b/oauth2_http/testresources/mtls/certificate_config.json new file mode 100644 index 000000000..8a7592faf --- /dev/null +++ b/oauth2_http/testresources/mtls/certificate_config.json @@ -0,0 +1,8 @@ +{ + "cert_configs": { + "workload": { + "cert_path": "testresources/mtls/test_cert.pem", + "key_path": "testresources/mtls/test_key.pem" + } + } +} diff --git a/oauth2_http/testresources/mtls/test_cert.pem b/oauth2_http/testresources/mtls/test_cert.pem new file mode 100644 index 000000000..17fcf0227 --- /dev/null +++ b/oauth2_http/testresources/mtls/test_cert.pem @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICGzCCAYSgAwIBAgIIWrt6xtmHPs4wDQYJKoZIhvcNAQEFBQAwMzExMC8GA1UE +AxMoMTAwOTEyMDcyNjg3OC5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbTAeFw0x +MjEyMDExNjEwNDRaFw0yMjExMjkxNjEwNDRaMDMxMTAvBgNVBAMTKDEwMDkxMjA3 +MjY4NzguYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20wgZ8wDQYJKoZIhvcNAQEB +BQADgY0AMIGJAoGBAL1SdY8jTUVU7O4/XrZLYTw0ON1lV6MQRGajFDFCqD2Fd9tQ +GLW8Iftx9wfXe1zuaehJSgLcyCxazfyJoN3RiONBihBqWY6d3lQKqkgsRTNZkdFJ +Wdzl/6CxhK9sojh2p0r3tydtv9iwq5fuuWIvtODtT98EgphhncQAqkKoF3zVAgMB +AAGjODA2MAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgeAMBYGA1UdJQEB/wQM +MAoGCCsGAQUFBwMCMA0GCSqGSIb3DQEBBQUAA4GBAD8XQEqzGePa9VrvtEGpf+R4 +fkxKbcYAzqYq202nKu0kfjhIYkYSBj6gi348YaxE64yu60TVl42l5HThmswUheW4 +uQIaq36JvwvsDP5Zoj5BgiNSnDAFQp+jJFBRUA5vooJKgKgMDf/r/DCOsbO6VJF1 +kWwa9n19NFiV0z3m6isj +-----END CERTIFICATE----- diff --git a/oauth2_http/testresources/mtls/test_key.pem b/oauth2_http/testresources/mtls/test_key.pem new file mode 100644 index 000000000..c6a91c3ce --- /dev/null +++ b/oauth2_http/testresources/mtls/test_key.pem @@ -0,0 +1,16 @@ +-----BEGIN PRIVATE KEY----- +MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAL1SdY8jTUVU7O4/ +XrZLYTw0ON1lV6MQRGajFDFCqD2Fd9tQGLW8Iftx9wfXe1zuaehJSgLcyCxazfyJ +oN3RiONBihBqWY6d3lQKqkgsRTNZkdFJWdzl/6CxhK9sojh2p0r3tydtv9iwq5fu +uWIvtODtT98EgphhncQAqkKoF3zVAgMBAAECgYB51B9cXe4yiGTzJ4pOKpHGySAy +sC1F/IjXt2eeD3PuKv4m/hL4l7kScpLx0+NJuQ4j8U2UK/kQOdrGANapB1ZbMZAK +/q0xmIUzdNIDiGSoTXGN2mEfdsEpQ/Xiv0lyhYBBPC/K4sYIpHccnhSRQUZlWLLY +lE5cFNKC9b7226mNvQJBAPt0hfCNIN0kUYOA9jdLtx7CE4ySGMPf5KPBuzPd8ty1 +fxaFm9PB7B76VZQYmHcWy8rT5XjoLJHrmGW1ZvP+iDsCQQDAvnKoarPOGb5iJfkq +RrA4flf1TOlf+1+uqIOJ94959jkkJeb0gv/TshDnm6/bWn+1kJylQaKygCizwPwB +Z84vAkA0Duur4YvsPJijoQ9YY1SGCagCcjyuUKwFOxaGpmyhRPIKt56LOJqpzyno +fy8ReKa4VyYq4eZYT249oFCwMwIBAkAROPNF2UL3x5UbcAkznd1hLujtIlI4IV4L +XUNjsJtBap7we/KHJq11XRPlniO4lf2TW7iji5neGVWJulTKS1xBAkAerktk4Hsw +ErUaUG1s/d+Sgc8e/KMeBElV+NxGhcWEeZtfHMn/6VOlbzY82JyvC9OKC80A5CAE +VUV6b25kqrcu +-----END PRIVATE KEY-----