From 2519404a2b7cb2090fd2c44dcf6f1ea76f4ec0c1 Mon Sep 17 00:00:00 2001 From: Pranav Iyer Date: Mon, 23 Mar 2026 19:17:57 -0700 Subject: [PATCH 1/9] feat: Refactor X509Provider --- .../java/com/google/auth/mtls/MtlsUtils.java | 87 +++++++++ .../com/google/auth/mtls/X509Provider.java | 123 ++++--------- .../google/auth/oauth2/AwsCredentials.java | 4 - .../auth/oauth2/EnvironmentProvider.java | 8 +- .../oauth2/ExternalAccountCredentials.java | 24 +++ .../auth/oauth2/IdentityPoolCredentials.java | 55 ++---- .../google/auth/oauth2/PropertyProvider.java | 10 + .../oauth2/SystemEnvironmentProvider.java | 8 +- .../auth/oauth2/SystemPropertyProvider.java | 24 +++ .../google/auth/mtls/X509ProviderTest.java | 173 ++++-------------- .../oauth2/IdentityPoolCredentialsTest.java | 129 +------------ .../auth/oauth2/TestEnvironmentProvider.java | 2 +- .../auth/oauth2/TestPropertyProvider.java | 22 +++ .../mtls/certificate_config.json | 8 + oauth2_http/testresources/mtls/test_cert.pem | 14 ++ oauth2_http/testresources/mtls/test_key.pem | 16 ++ 16 files changed, 312 insertions(+), 395 deletions(-) create mode 100644 oauth2_http/java/com/google/auth/mtls/MtlsUtils.java create mode 100644 oauth2_http/java/com/google/auth/oauth2/PropertyProvider.java create mode 100644 oauth2_http/java/com/google/auth/oauth2/SystemPropertyProvider.java create mode 100644 oauth2_http/javatests/com/google/auth/oauth2/TestPropertyProvider.java create mode 100644 oauth2_http/testresources/mtls/certificate_config.json create mode 100644 oauth2_http/testresources/mtls/test_cert.pem create mode 100644 oauth2_http/testresources/mtls/test_key.pem 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..8c0bec44d --- /dev/null +++ b/oauth2_http/java/com/google/auth/mtls/MtlsUtils.java @@ -0,0 +1,87 @@ +package com.google.auth.mtls; + +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. + */ +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; + } + + public 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("File does not exist."); + } + 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..a532c36e1 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.auth.oauth2.EnvironmentProvider; +import com.google.auth.oauth2.PropertyProvider; +import com.google.auth.oauth2.SystemEnvironmentProvider; +import com.google.auth.oauth2.SystemPropertyProvider; import com.google.common.base.Strings; 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 @@ -48,10 +50,8 @@ * backwards compatibility. */ 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 +59,31 @@ 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) { + 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 @@ -78,23 +97,15 @@ public X509Provider() { * 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. + *

If the configuration has not been loaded yet, 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; + return MtlsUtils.getCertificatePath(envProvider, propProvider, certConfigPathOverride); } /** @@ -115,12 +126,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)) { @@ -150,73 +163,5 @@ 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..9e4692236 100644 --- a/oauth2_http/java/com/google/auth/oauth2/AwsCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/AwsCredentials.java @@ -196,10 +196,6 @@ String getRegionalCredentialVerificationUrl() { return this.regionalCredentialVerificationUrl; } - @VisibleForTesting - String getEnv(String name) { - return System.getenv(name); - } @VisibleForTesting AwsSecurityCredentialsSupplier getAwsSecurityCredentialsSupplier() { diff --git a/oauth2_http/java/com/google/auth/oauth2/EnvironmentProvider.java b/oauth2_http/java/com/google/auth/oauth2/EnvironmentProvider.java index 5c77ecc65..0f1caa302 100644 --- a/oauth2_http/java/com/google/auth/oauth2/EnvironmentProvider.java +++ b/oauth2_http/java/com/google/auth/oauth2/EnvironmentProvider.java @@ -1,6 +1,10 @@ package com.google.auth.oauth2; -/** Interface for an environment provider. */ -interface EnvironmentProvider { +/** + * Interface for an environment provider. + * + *

For internal use only. + */ +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..6657a36fd 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; @@ -95,7 +96,7 @@ public class IdentityPoolCredentials extends ExternalAccountCredentials { == IdentityPoolCredentialSourceType.CERTIFICATE) { try { this.subjectTokenSupplier = - createCertificateSubjectTokenSupplier(builder, credentialSource); + createCertificateSubjectTokenSupplier(credentialSource); } catch (IOException e) { throw new RuntimeException( // Wrap IOException in RuntimeException because constructors cannot throw checked @@ -159,39 +160,31 @@ public Builder toBuilder() { } private IdentityPoolSubjectTokenSupplier createCertificateSubjectTokenSupplier( - Builder builder, IdentityPoolCredentialSource credentialSource) throws IOException { + IdentityPoolCredentialSource credentialSource) throws IOException { + final IdentityPoolCredentialSource.CertificateConfig certConfig = + credentialSource.getCertificateConfig(); + String explicitCertConfigPath = + certConfig.useDefaultCertificateConfig() + ? null + : certConfig.getCertificateConfigLocation(); + // Configure the mTLS transport with the x509 keystore. - X509Provider x509Provider = getX509Provider(builder, credentialSource); + X509Provider x509Provider = + new X509Provider(getEnvironmentProvider(), getPropertyProvider(), explicitCertConfigPath); KeyStore mtlsKeyStore = x509Provider.getKeyStore(); this.transportFactory = new MtlsHttpTransportFactory(mtlsKeyStore); // Initialize the subject token supplier with the certificate path. - credentialSource.setCredentialLocation(x509Provider.getCertificatePath()); + String configCertPath = + MtlsUtils.getCertificatePath( + getEnvironmentProvider(), getPropertyProvider(), explicitCertConfigPath); + credentialSource.setCredentialLocation(configCertPath); return new CertificateIdentityPoolSubjectTokenSupplier(credentialSource); } - private X509Provider getX509Provider( - Builder builder, IdentityPoolCredentialSource credentialSource) { - final IdentityPoolCredentialSource.CertificateConfig certConfig = - credentialSource.getCertificateConfig(); - - // Use the provided X509Provider if available, otherwise initialize a default one. - 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); - } - return x509Provider; - } - public static class Builder extends ExternalAccountCredentials.Builder { private IdentityPoolSubjectTokenSupplier subjectTokenSupplier; - private X509Provider x509Provider; Builder() {} @@ -202,20 +195,8 @@ public static class Builder extends ExternalAccountCredentials.Builder { } } - /** - * Sets a custom {@link X509Provider} to manage the client certificate and private key for mTLS. - * If set, this provider will be used instead of the default behavior which initializes an - * {@code X509Provider} based on the {@code certificateConfigLocation} or default paths found in - * the {@code credentialSource}. This is primarily used for testing. - * - * @param x509Provider the custom X509 provider to use. - * @return this {@code Builder} object - */ - @CanIgnoreReturnValue - Builder setX509Provider(X509Provider x509Provider) { - this.x509Provider = x509Provider; - return this; - } + + /** * Sets the subject token supplier. The supplier should return a valid subject token string. 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..8f956a0a3 --- /dev/null +++ b/oauth2_http/java/com/google/auth/oauth2/PropertyProvider.java @@ -0,0 +1,10 @@ +package com.google.auth.oauth2; + +/** + * Interface for a system property provider. + * + *

For internal use only. + */ +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..75c5ed203 100644 --- a/oauth2_http/java/com/google/auth/oauth2/SystemEnvironmentProvider.java +++ b/oauth2_http/java/com/google/auth/oauth2/SystemEnvironmentProvider.java @@ -2,8 +2,12 @@ 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. + */ +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..eacd7cf01 --- /dev/null +++ b/oauth2_http/java/com/google/auth/oauth2/SystemPropertyProvider.java @@ -0,0 +1,24 @@ +package com.google.auth.oauth2; + +import java.io.Serializable; + +/** + * Represents the default system property provider. + * + *

For internal use only. + */ +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..ca21909c2 100644 --- a/oauth2_http/javatests/com/google/auth/mtls/X509ProviderTest.java +++ b/oauth2_http/javatests/com/google/auth/mtls/X509ProviderTest.java @@ -36,60 +36,31 @@ 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.SystemEnvironmentProvider; +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); + X509Provider testProvider = new X509Provider(certConfigPath); String expectedErrorMessage = "File does not exist."; CertificateSourceUnavailableException exception = @@ -98,11 +69,11 @@ void x509Provider_fileDoesntExist_throws() { } @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,76 +121,21 @@ 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(); assertEquals(1, store.size()); 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<>(); - } - - void addFile(String file, InputStream stream) { - files.put(file, stream); - } - - @Override - String getEnv(String name) { - return variables.get(name); - } - - void setEnv(String name, String value) { - variables.put(name, value); - } - - @Override - String getProperty(String property, String def) { - String value = properties.get(property); - return value == null ? def : value; - } - - @Override - boolean isFile(File file) { - return files.containsKey(file.getPath()); - } - - @Override - InputStream createInputStream(File file) throws FileNotFoundException { - InputStream stream = files.get(file.getPath()); - if (stream == null) { - throw new FileNotFoundException(file.getPath()); - } - return stream; - } - } } diff --git a/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java index 6997c79b0..8651d84a6 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java @@ -1045,17 +1045,11 @@ 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", true); + 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 = @@ -1063,10 +1057,9 @@ void build_withCertificateSourceAndCustomX509Provider_success() MockExternalAccountCredentialsTransportFactory mockTransportFactory = new MockExternalAccountCredentialsTransportFactory(); - // Build credentials with the custom provider. + // Build credentials. IdentityPoolCredentials credentials = IdentityPoolCredentials.newBuilder() - .setX509Provider(x509Provider) .setHttpTransportFactory(mockTransportFactory) .setAudience("test-audience") .setSubjectTokenType("test-token-type") @@ -1083,10 +1076,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 @@ -1115,81 +1104,6 @@ void build_withDefaultCertificate_throwsOnTransportInitFailure() { exception.getMessage()); } - @Test - void build_withCustomProvider_throwsOnGetKeyStore() - throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException { - // Simulate a scenario where the X509Provider fails to load the KeyStore, typically due to an - // IOException when reading the certificate or private key files. - KeyStore keyStore = KeyStore.getInstance("JKS"); - keyStore.load(null, null); - TestX509Provider x509Provider = new TestX509Provider(keyStore, "/path/to/certificate.json"); - x509Provider.setShouldThrowOnGetKeyStore(true); // Configure to throw - - Map certificateMap = new HashMap<>(); - certificateMap.put("certificate_config_location", "/path/to/certificate.json"); - - // Expect RuntimeException because the constructor wraps the IOException. - RuntimeException exception = - assertThrows( - RuntimeException.class, - () -> createCredentialsWithCertificate(x509Provider, certificateMap)); - - // Verify the cause is the expected IOException from the mock. - assertNotNull(exception.getCause()); - assertTrue(exception.getCause() instanceof IOException); - assertEquals("Simulated IOException on get keystore", exception.getCause().getMessage()); - - // Verify the wrapper exception message - assertEquals( - "Failed to initialize IdentityPoolCredentials from certificate source due to an I/O error.", - exception.getMessage()); - } - - @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. - 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"); - - // Expect RuntimeException because the constructor wraps the IOException. - RuntimeException exception = - assertThrows( - RuntimeException.class, - () -> createCredentialsWithCertificate(x509Provider, certificateMap)); - - // Verify the cause is the expected IOException from the mock. - assertNotNull(exception.getCause()); - assertTrue(exception.getCause() instanceof IOException); - assertEquals("Simulated IOException on certificate path", exception.getCause().getMessage()); - - // Verify the wrapper exception message - assertEquals( - "Failed to initialize IdentityPoolCredentials from certificate source due to an I/O error.", - exception.getMessage()); - } - - private void createCredentialsWithCertificate( - X509Provider x509Provider, Map certificateMap) { - Map credentialSourceMap = new HashMap<>(); - credentialSourceMap.put("certificate", certificateMap); - IdentityPoolCredentialSource credentialSource = - new IdentityPoolCredentialSource(credentialSourceMap); - - IdentityPoolCredentials.newBuilder() - .setX509Provider(x509Provider) - .setHttpTransportFactory(new MockExternalAccountCredentialsTransportFactory()) - .setAudience("") - .setSubjectTokenType("") - .setCredentialSource(credentialSource) - .build(); - } static InputStream writeIdentityPoolCredentialsStream( String tokenUrl, @@ -1272,40 +1186,5 @@ public HttpTransport create() { } } - 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(); - this.keyStore = keyStore; - this.certificatePath = certificatePath; - } - - @Override - public KeyStore getKeyStore() throws IOException { - if (shouldThrowOnGetKeyStore) { - throw new IOException("Simulated IOException on get keystore"); - } - 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..bea483dc7 --- /dev/null +++ b/oauth2_http/javatests/com/google/auth/oauth2/TestPropertyProvider.java @@ -0,0 +1,22 @@ +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----- From 2576ae96bf98d9560e1af1ff232b23432180161a Mon Sep 17 00:00:00 2001 From: Pranav Iyer Date: Mon, 23 Mar 2026 19:49:46 -0700 Subject: [PATCH 2/9] Added back accidentally removed tests. --- .../auth/oauth2/IdentityPoolCredentials.java | 57 +++++-- .../oauth2/IdentityPoolCredentialsTest.java | 155 ++++++++++++++++++ 2 files changed, 194 insertions(+), 18 deletions(-) diff --git a/oauth2_http/java/com/google/auth/oauth2/IdentityPoolCredentials.java b/oauth2_http/java/com/google/auth/oauth2/IdentityPoolCredentials.java index 6657a36fd..d21fc17a5 100644 --- a/oauth2_http/java/com/google/auth/oauth2/IdentityPoolCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/IdentityPoolCredentials.java @@ -96,7 +96,7 @@ public class IdentityPoolCredentials extends ExternalAccountCredentials { == IdentityPoolCredentialSourceType.CERTIFICATE) { try { this.subjectTokenSupplier = - createCertificateSubjectTokenSupplier(credentialSource); + createCertificateSubjectTokenSupplier(builder, credentialSource); } catch (IOException e) { throw new RuntimeException( // Wrap IOException in RuntimeException because constructors cannot throw checked @@ -160,31 +160,40 @@ public Builder toBuilder() { } private IdentityPoolSubjectTokenSupplier createCertificateSubjectTokenSupplier( - IdentityPoolCredentialSource credentialSource) throws IOException { - final IdentityPoolCredentialSource.CertificateConfig certConfig = - credentialSource.getCertificateConfig(); - String explicitCertConfigPath = - certConfig.useDefaultCertificateConfig() - ? null - : certConfig.getCertificateConfigLocation(); - + Builder builder, IdentityPoolCredentialSource credentialSource) throws IOException { // Configure the mTLS transport with the x509 keystore. - X509Provider x509Provider = - new X509Provider(getEnvironmentProvider(), getPropertyProvider(), explicitCertConfigPath); + X509Provider x509Provider = getX509Provider(builder, credentialSource); KeyStore mtlsKeyStore = x509Provider.getKeyStore(); this.transportFactory = new MtlsHttpTransportFactory(mtlsKeyStore); // Initialize the subject token supplier with the certificate path. - String configCertPath = - MtlsUtils.getCertificatePath( - getEnvironmentProvider(), getPropertyProvider(), explicitCertConfigPath); - credentialSource.setCredentialLocation(configCertPath); + credentialSource.setCredentialLocation(x509Provider.getCertificatePath()); return new CertificateIdentityPoolSubjectTokenSupplier(credentialSource); } + private X509Provider getX509Provider( + Builder builder, IdentityPoolCredentialSource credentialSource) { + final IdentityPoolCredentialSource.CertificateConfig certConfig = + credentialSource.getCertificateConfig(); + + // Use the provided X509Provider if available, otherwise initialize a default one. + 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(getEnvironmentProvider(), getPropertyProvider(), explicitCertConfigPath); + } + return x509Provider; + } + public static class Builder extends ExternalAccountCredentials.Builder { private IdentityPoolSubjectTokenSupplier subjectTokenSupplier; + private X509Provider x509Provider; Builder() {} @@ -195,9 +204,21 @@ public static class Builder extends ExternalAccountCredentials.Builder { } } - - - + /** + * Sets a custom {@link X509Provider} to manage the client certificate and private key for mTLS. + * If set, this provider will be used instead of the default behavior which initializes an + * {@code X509Provider} based on the {@code certificateConfigLocation} or default paths found in + * the {@code credentialSource}. This is primarily used for testing. + * + * @param x509Provider the custom X509 provider to use. + * @return this {@code Builder} object + */ + @CanIgnoreReturnValue + @VisibleForTesting + Builder setX509Provider(X509Provider x509Provider) { + this.x509Provider = x509Provider; + return this; + } /** * Sets the subject token supplier. The supplier should return a valid subject token string. * diff --git a/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java index 8651d84a6..6404a99d9 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java @@ -1078,6 +1078,51 @@ void build_withCertificateSource_succeeds() throws Exception { "Metrics header should indicate certificate source"); } + @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")); + + // Set up credential source for certificate type. + Map certificateMap = new HashMap<>(); + certificateMap.put("use_default_certificate_config", true); + Map credentialSourceMap = new HashMap<>(); + credentialSourceMap.put("certificate", certificateMap); + IdentityPoolCredentialSource credentialSource = + new IdentityPoolCredentialSource(credentialSourceMap); + MockExternalAccountCredentialsTransportFactory mockTransportFactory = + new MockExternalAccountCredentialsTransportFactory(); + + // Build credentials with the custom provider. + IdentityPoolCredentials credentials = + IdentityPoolCredentials.newBuilder() + .setX509Provider(x509Provider) + .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"); + + // Verify the custom provider methods were called during build. + verify(x509Provider).getKeyStore(); + verify(x509Provider).getCertificatePath(); + } + @Test void build_withDefaultCertificate_throwsOnTransportInitFailure() { // Setup credential source to use default certificate config. @@ -1104,6 +1149,81 @@ void build_withDefaultCertificate_throwsOnTransportInitFailure() { exception.getMessage()); } + @Test + void build_withCustomProvider_throwsOnGetKeyStore() + throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException { + // Simulate a scenario where the X509Provider fails to load the KeyStore, typically due to an + // IOException when reading the certificate or private key files. + KeyStore keyStore = KeyStore.getInstance("JKS"); + keyStore.load(null, null); + TestX509Provider x509Provider = new TestX509Provider(keyStore, "/path/to/certificate.json"); + x509Provider.setShouldThrowOnGetKeyStore(true); // Configure to throw + + Map certificateMap = new HashMap<>(); + certificateMap.put("certificate_config_location", "/path/to/certificate.json"); + + // Expect RuntimeException because the constructor wraps the IOException. + RuntimeException exception = + assertThrows( + RuntimeException.class, + () -> createCredentialsWithCertificate(x509Provider, certificateMap)); + + // Verify the cause is the expected IOException from the mock. + assertNotNull(exception.getCause()); + assertTrue(exception.getCause() instanceof IOException); + assertEquals("Simulated IOException on get keystore", exception.getCause().getMessage()); + + // Verify the wrapper exception message + assertEquals( + "Failed to initialize IdentityPoolCredentials from certificate source due to an I/O error.", + exception.getMessage()); + } + + @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. + 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"); + + // Expect RuntimeException because the constructor wraps the IOException. + RuntimeException exception = + assertThrows( + RuntimeException.class, + () -> createCredentialsWithCertificate(x509Provider, certificateMap)); + + // Verify the cause is the expected IOException from the mock. + assertNotNull(exception.getCause()); + assertTrue(exception.getCause() instanceof IOException); + assertEquals("Simulated IOException on certificate path", exception.getCause().getMessage()); + + // Verify the wrapper exception message + assertEquals( + "Failed to initialize IdentityPoolCredentials from certificate source due to an I/O error.", + exception.getMessage()); + } + + private void createCredentialsWithCertificate( + X509Provider x509Provider, Map certificateMap) { + Map credentialSourceMap = new HashMap<>(); + credentialSourceMap.put("certificate", certificateMap); + IdentityPoolCredentialSource credentialSource = + new IdentityPoolCredentialSource(credentialSourceMap); + + IdentityPoolCredentials.newBuilder() + .setX509Provider(x509Provider) + .setHttpTransportFactory(new MockExternalAccountCredentialsTransportFactory()) + .setAudience("") + .setSubjectTokenType("") + .setCredentialSource(credentialSource) + .build(); + } static InputStream writeIdentityPoolCredentialsStream( String tokenUrl, @@ -1186,5 +1306,40 @@ public HttpTransport create() { } } + static class TestX509Provider extends X509Provider { + private final KeyStore keyStore; + private final String certPath; + private boolean throwOnGetKeyStore; + private boolean throwOnGetCertificatePath; + + public TestX509Provider(KeyStore keyStore, String certPath) { + super(); + this.keyStore = keyStore; + this.certPath = certPath; + } + + public void setShouldThrowOnGetKeyStore(boolean throwOnGetKeyStore) { + this.throwOnGetKeyStore = throwOnGetKeyStore; + } + + public void setShouldThrowOnGetCertificatePath(boolean throwOnGetCertificatePath) { + this.throwOnGetCertificatePath = throwOnGetCertificatePath; + } + + @Override + public KeyStore getKeyStore() throws IOException { + if (throwOnGetKeyStore) { + throw new IOException("Simulated IOException on get keystore"); + } + return keyStore; + } + @Override + public String getCertificatePath() throws IOException { + if (throwOnGetCertificatePath) { + throw new IOException("Simulated IOException on certificate path"); + } + return certPath; + } + } } From f78ed98ab755744d2df45820d6dcd7adc5e0079d Mon Sep 17 00:00:00 2001 From: Pranav Iyer Date: Mon, 23 Mar 2026 19:59:42 -0700 Subject: [PATCH 3/9] Lint fixes --- .../com/google/auth/mtls/X509Provider.java | 3 -- .../google/auth/oauth2/AwsCredentials.java | 1 - .../auth/oauth2/EnvironmentProvider.java | 2 +- .../auth/oauth2/IdentityPoolCredentials.java | 2 +- .../google/auth/oauth2/PropertyProvider.java | 2 +- .../auth/oauth2/SystemPropertyProvider.java | 2 +- .../google/auth/mtls/X509ProviderTest.java | 2 -- .../oauth2/IdentityPoolCredentialsTest.java | 36 +++++++++---------- 8 files changed, 22 insertions(+), 28 deletions(-) diff --git a/oauth2_http/java/com/google/auth/mtls/X509Provider.java b/oauth2_http/java/com/google/auth/mtls/X509Provider.java index a532c36e1..2bd8444b2 100644 --- a/oauth2_http/java/com/google/auth/mtls/X509Provider.java +++ b/oauth2_http/java/com/google/auth/mtls/X509Provider.java @@ -35,7 +35,6 @@ import com.google.auth.oauth2.PropertyProvider; import com.google.auth.oauth2.SystemEnvironmentProvider; import com.google.auth.oauth2.SystemPropertyProvider; -import com.google.common.base.Strings; import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -162,6 +161,4 @@ public boolean isAvailable() throws IOException { } return true; } - - } diff --git a/oauth2_http/java/com/google/auth/oauth2/AwsCredentials.java b/oauth2_http/java/com/google/auth/oauth2/AwsCredentials.java index 9e4692236..bb943fcdf 100644 --- a/oauth2_http/java/com/google/auth/oauth2/AwsCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/AwsCredentials.java @@ -196,7 +196,6 @@ String getRegionalCredentialVerificationUrl() { return this.regionalCredentialVerificationUrl; } - @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 0f1caa302..160ea75b6 100644 --- a/oauth2_http/java/com/google/auth/oauth2/EnvironmentProvider.java +++ b/oauth2_http/java/com/google/auth/oauth2/EnvironmentProvider.java @@ -2,7 +2,7 @@ /** * Interface for an environment provider. - * + * *

For internal use only. */ public interface EnvironmentProvider { diff --git a/oauth2_http/java/com/google/auth/oauth2/IdentityPoolCredentials.java b/oauth2_http/java/com/google/auth/oauth2/IdentityPoolCredentials.java index d21fc17a5..afb6ebce9 100644 --- a/oauth2_http/java/com/google/auth/oauth2/IdentityPoolCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/IdentityPoolCredentials.java @@ -33,7 +33,6 @@ 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; @@ -219,6 +218,7 @@ Builder setX509Provider(X509Provider x509Provider) { this.x509Provider = x509Provider; return this; } + /** * Sets the subject token supplier. The supplier should return a valid subject token string. * diff --git a/oauth2_http/java/com/google/auth/oauth2/PropertyProvider.java b/oauth2_http/java/com/google/auth/oauth2/PropertyProvider.java index 8f956a0a3..fb8f9cd96 100644 --- a/oauth2_http/java/com/google/auth/oauth2/PropertyProvider.java +++ b/oauth2_http/java/com/google/auth/oauth2/PropertyProvider.java @@ -2,7 +2,7 @@ /** * Interface for a system property provider. - * + * *

For internal use only. */ public interface PropertyProvider { diff --git a/oauth2_http/java/com/google/auth/oauth2/SystemPropertyProvider.java b/oauth2_http/java/com/google/auth/oauth2/SystemPropertyProvider.java index eacd7cf01..ed43c2829 100644 --- a/oauth2_http/java/com/google/auth/oauth2/SystemPropertyProvider.java +++ b/oauth2_http/java/com/google/auth/oauth2/SystemPropertyProvider.java @@ -4,7 +4,7 @@ /** * Represents the default system property provider. - * + * *

For internal use only. */ public class SystemPropertyProvider implements PropertyProvider, Serializable { diff --git a/oauth2_http/javatests/com/google/auth/mtls/X509ProviderTest.java b/oauth2_http/javatests/com/google/auth/mtls/X509ProviderTest.java index ca21909c2..f9494cd16 100644 --- a/oauth2_http/javatests/com/google/auth/mtls/X509ProviderTest.java +++ b/oauth2_http/javatests/com/google/auth/mtls/X509ProviderTest.java @@ -36,10 +36,8 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import com.google.auth.oauth2.SystemEnvironmentProvider; 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.FileInputStream; import java.io.IOException; diff --git a/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java index 6404a99d9..c9a19fbfd 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java @@ -1212,7 +1212,7 @@ void build_withCustomProvider_throwsOnGetCertificatePath() private void createCredentialsWithCertificate( X509Provider x509Provider, Map certificateMap) { Map credentialSourceMap = new HashMap<>(); - credentialSourceMap.put("certificate", certificateMap); + credentialSourceMap.put("certificate", certificateMap); IdentityPoolCredentialSource credentialSource = new IdentityPoolCredentialSource(credentialSourceMap); @@ -1306,29 +1306,21 @@ public HttpTransport create() { } } - static class TestX509Provider extends X509Provider { + private static class TestX509Provider extends X509Provider { private final KeyStore keyStore; - private final String certPath; - private boolean throwOnGetKeyStore; - private boolean throwOnGetCertificatePath; + private final String certificatePath; + private boolean shouldThrowOnGetKeyStore = false; + private boolean shouldThrowOnGetCertificatePath = false; - public TestX509Provider(KeyStore keyStore, String certPath) { + TestX509Provider(KeyStore keyStore, String certificatePath) { super(); this.keyStore = keyStore; - this.certPath = certPath; - } - - public void setShouldThrowOnGetKeyStore(boolean throwOnGetKeyStore) { - this.throwOnGetKeyStore = throwOnGetKeyStore; - } - - public void setShouldThrowOnGetCertificatePath(boolean throwOnGetCertificatePath) { - this.throwOnGetCertificatePath = throwOnGetCertificatePath; + this.certificatePath = certificatePath; } @Override public KeyStore getKeyStore() throws IOException { - if (throwOnGetKeyStore) { + if (shouldThrowOnGetKeyStore) { throw new IOException("Simulated IOException on get keystore"); } return keyStore; @@ -1336,10 +1328,18 @@ public KeyStore getKeyStore() throws IOException { @Override public String getCertificatePath() throws IOException { - if (throwOnGetCertificatePath) { + if (shouldThrowOnGetCertificatePath) { throw new IOException("Simulated IOException on certificate path"); } - return certPath; + return certificatePath; + } + + void setShouldThrowOnGetKeyStore(boolean shouldThrow) { + this.shouldThrowOnGetKeyStore = shouldThrow; + } + + void setShouldThrowOnGetCertificatePath(boolean shouldThrow) { + this.shouldThrowOnGetCertificatePath = shouldThrow; } } } From 116a64a8a70accbece6240dae6428cda9822388b Mon Sep 17 00:00:00 2001 From: Pranav Iyer Date: Tue, 24 Mar 2026 14:19:29 -0700 Subject: [PATCH 4/9] Addressed the PR comments. --- .../java/com/google/auth/mtls/MtlsUtils.java | 50 +++++++++++++++++-- .../com/google/auth/mtls/X509Provider.java | 2 + .../auth/oauth2/EnvironmentProvider.java | 3 ++ .../google/auth/oauth2/PropertyProvider.java | 3 ++ 4 files changed, 55 insertions(+), 3 deletions(-) diff --git a/oauth2_http/java/com/google/auth/mtls/MtlsUtils.java b/oauth2_http/java/com/google/auth/mtls/MtlsUtils.java index 8c0bec44d..0dfb4fb6e 100644 --- a/oauth2_http/java/com/google/auth/mtls/MtlsUtils.java +++ b/oauth2_http/java/com/google/auth/mtls/MtlsUtils.java @@ -1,3 +1,33 @@ +/* + * 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.auth.oauth2.EnvironmentProvider; @@ -14,7 +44,7 @@ * *

For internal use only. */ -public class MtlsUtils { +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"; @@ -30,7 +60,7 @@ private MtlsUtils() { * @return The path to the certificate file. * @throws IOException if the certificate configuration cannot be found or loaded. */ - public static String getCertificatePath( + static String getCertificatePath( EnvironmentProvider envProvider, PropertyProvider propProvider, String certConfigPathOverride) throws IOException { String certPath = @@ -43,7 +73,21 @@ public static String getCertificatePath( return certPath; } - public static WorkloadCertificateConfiguration getWorkloadCertificateConfiguration( + /** + * 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; diff --git a/oauth2_http/java/com/google/auth/mtls/X509Provider.java b/oauth2_http/java/com/google/auth/mtls/X509Provider.java index 2bd8444b2..867766b00 100644 --- a/oauth2_http/java/com/google/auth/mtls/X509Provider.java +++ b/oauth2_http/java/com/google/auth/mtls/X509Provider.java @@ -31,6 +31,7 @@ package com.google.auth.mtls; import com.google.api.client.util.SecurityUtils; +import com.google.api.core.InternalApi; import com.google.auth.oauth2.EnvironmentProvider; import com.google.auth.oauth2.PropertyProvider; import com.google.auth.oauth2.SystemEnvironmentProvider; @@ -48,6 +49,7 @@ * libraries, and the public facing methods may be changed without notice, and have no guarantee of * backwards compatibility. */ +@InternalApi public class X509Provider implements MtlsProvider { private final EnvironmentProvider envProvider; private final PropertyProvider propProvider; diff --git a/oauth2_http/java/com/google/auth/oauth2/EnvironmentProvider.java b/oauth2_http/java/com/google/auth/oauth2/EnvironmentProvider.java index 160ea75b6..f4a059117 100644 --- a/oauth2_http/java/com/google/auth/oauth2/EnvironmentProvider.java +++ b/oauth2_http/java/com/google/auth/oauth2/EnvironmentProvider.java @@ -1,10 +1,13 @@ package com.google.auth.oauth2; +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/PropertyProvider.java b/oauth2_http/java/com/google/auth/oauth2/PropertyProvider.java index fb8f9cd96..bfb833b81 100644 --- a/oauth2_http/java/com/google/auth/oauth2/PropertyProvider.java +++ b/oauth2_http/java/com/google/auth/oauth2/PropertyProvider.java @@ -1,10 +1,13 @@ 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); } From 7ebab7c4bb59df5610a9ab1095fd8540ca458f53 Mon Sep 17 00:00:00 2001 From: Pranav Iyer Date: Thu, 26 Mar 2026 13:42:31 -0400 Subject: [PATCH 5/9] Test for windows OS cert default path --- .../google/auth/mtls/X509ProviderTest.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/oauth2_http/javatests/com/google/auth/mtls/X509ProviderTest.java b/oauth2_http/javatests/com/google/auth/mtls/X509ProviderTest.java index f9494cd16..32136416e 100644 --- a/oauth2_http/javatests/com/google/auth/mtls/X509ProviderTest.java +++ b/oauth2_http/javatests/com/google/auth/mtls/X509ProviderTest.java @@ -38,6 +38,7 @@ 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.FileInputStream; import java.io.IOException; @@ -136,4 +137,36 @@ void x509Provider_succeeds_withWellKnownPath() assertEquals(1, store.size()); assertNotNull(store.getCertificateAlias(expectedCert)); } + + @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"); + + // Copy the valid config to this new temp location + Files.copy(new File(TEST_CONFIG_PATH).toPath(), configPath); + + TestEnvironmentProvider envProvider = new TestEnvironmentProvider(); + envProvider.setEnv("APPDATA", windowsTempDir.toString()); + + TestPropertyProvider propProvider = new TestPropertyProvider(); + propProvider.setProperty("os.name", "Windows 10"); + + X509Provider testProvider = new X509Provider(envProvider, propProvider, null); + + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + Certificate expectedCert; + try (FileInputStream fis = new FileInputStream(new File(TEST_CERT_PATH))) { + expectedCert = cf.generateCertificate(fis); + } + + KeyStore store = testProvider.getKeyStore(); + assertEquals(1, store.size()); + assertNotNull(store.getCertificateAlias(expectedCert)); + } } + From 735d43e79fc29df6bcace6df69832731dcab20a7 Mon Sep 17 00:00:00 2001 From: Pranav Iyer Date: Thu, 26 Mar 2026 13:43:30 -0400 Subject: [PATCH 6/9] lint fixes. --- oauth2_http/javatests/com/google/auth/mtls/X509ProviderTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/oauth2_http/javatests/com/google/auth/mtls/X509ProviderTest.java b/oauth2_http/javatests/com/google/auth/mtls/X509ProviderTest.java index 32136416e..bf71d7229 100644 --- a/oauth2_http/javatests/com/google/auth/mtls/X509ProviderTest.java +++ b/oauth2_http/javatests/com/google/auth/mtls/X509ProviderTest.java @@ -169,4 +169,3 @@ void x509Provider_succeeds_withWindowsPath() assertNotNull(store.getCertificateAlias(expectedCert)); } } - From aaa5ba6b0432f7421fa50e6abd66cadc9630dbe4 Mon Sep 17 00:00:00 2001 From: Pranav Iyer Date: Fri, 27 Mar 2026 10:17:22 -0400 Subject: [PATCH 7/9] Added Copyright headers. --- .../com/google/auth/mtls/X509Provider.java | 1 + .../google/auth/oauth2/PropertyProvider.java | 30 +++++++++++++++++ .../auth/oauth2/SystemPropertyProvider.java | 32 +++++++++++++++++++ .../auth/oauth2/TestPropertyProvider.java | 30 +++++++++++++++++ 4 files changed, 93 insertions(+) diff --git a/oauth2_http/java/com/google/auth/mtls/X509Provider.java b/oauth2_http/java/com/google/auth/mtls/X509Provider.java index 867766b00..1df7ab777 100644 --- a/oauth2_http/java/com/google/auth/mtls/X509Provider.java +++ b/oauth2_http/java/com/google/auth/mtls/X509Provider.java @@ -64,6 +64,7 @@ public class X509Provider implements MtlsProvider { * @param propProvider property provider used for system properties * @param certConfigPathOverride the path to read the certificate configuration from. */ + @InternalApi public X509Provider( EnvironmentProvider envProvider, PropertyProvider propProvider, diff --git a/oauth2_http/java/com/google/auth/oauth2/PropertyProvider.java b/oauth2_http/java/com/google/auth/oauth2/PropertyProvider.java index bfb833b81..fa4d2db07 100644 --- a/oauth2_http/java/com/google/auth/oauth2/PropertyProvider.java +++ b/oauth2_http/java/com/google/auth/oauth2/PropertyProvider.java @@ -1,3 +1,33 @@ +/* + * 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; diff --git a/oauth2_http/java/com/google/auth/oauth2/SystemPropertyProvider.java b/oauth2_http/java/com/google/auth/oauth2/SystemPropertyProvider.java index ed43c2829..f7cff5de5 100644 --- a/oauth2_http/java/com/google/auth/oauth2/SystemPropertyProvider.java +++ b/oauth2_http/java/com/google/auth/oauth2/SystemPropertyProvider.java @@ -1,5 +1,36 @@ +/* + * 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; /** @@ -7,6 +38,7 @@ * *

For internal use only. */ +@InternalApi public class SystemPropertyProvider implements PropertyProvider, Serializable { public static final SystemPropertyProvider INSTANCE = new SystemPropertyProvider(); private static final long serialVersionUID = 1L; diff --git a/oauth2_http/javatests/com/google/auth/oauth2/TestPropertyProvider.java b/oauth2_http/javatests/com/google/auth/oauth2/TestPropertyProvider.java index bea483dc7..b146874d1 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/TestPropertyProvider.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/TestPropertyProvider.java @@ -1,3 +1,33 @@ +/* + * 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; From 31f9a1197da553c79f06874be34cf9196a4a4dd0 Mon Sep 17 00:00:00 2001 From: Pranav Iyer Date: Fri, 27 Mar 2026 14:40:07 -0400 Subject: [PATCH 8/9] Addressed comments. --- .../java/com/google/auth/mtls/MtlsUtils.java | 10 ++++--- .../com/google/auth/mtls/X509Provider.java | 15 ----------- .../auth/oauth2/IdentityPoolCredentials.java | 19 +++++++++---- .../oauth2/SystemEnvironmentProvider.java | 2 ++ .../google/auth/mtls/X509ProviderTest.java | 7 ++--- .../oauth2/IdentityPoolCredentialsTest.java | 27 +++++-------------- 6 files changed, 34 insertions(+), 46 deletions(-) diff --git a/oauth2_http/java/com/google/auth/mtls/MtlsUtils.java b/oauth2_http/java/com/google/auth/mtls/MtlsUtils.java index 0dfb4fb6e..401822505 100644 --- a/oauth2_http/java/com/google/auth/mtls/MtlsUtils.java +++ b/oauth2_http/java/com/google/auth/mtls/MtlsUtils.java @@ -30,6 +30,7 @@ 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; @@ -44,7 +45,8 @@ * *

For internal use only. */ -class MtlsUtils { +@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"; @@ -60,7 +62,7 @@ private MtlsUtils() { * @return The path to the certificate file. * @throws IOException if the certificate configuration cannot be found or loaded. */ - static String getCertificatePath( + public static String getCertificatePath( EnvironmentProvider envProvider, PropertyProvider propProvider, String certConfigPathOverride) throws IOException { String certPath = @@ -103,7 +105,9 @@ static WorkloadCertificateConfiguration getWorkloadCertificateConfiguration( } if (!certConfig.isFile()) { - throw new CertificateSourceUnavailableException("File does not exist."); + 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); diff --git a/oauth2_http/java/com/google/auth/mtls/X509Provider.java b/oauth2_http/java/com/google/auth/mtls/X509Provider.java index 1df7ab777..4127b1492 100644 --- a/oauth2_http/java/com/google/auth/mtls/X509Provider.java +++ b/oauth2_http/java/com/google/auth/mtls/X509Provider.java @@ -95,21 +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, 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. - */ - public String getCertificatePath() throws IOException { - return MtlsUtils.getCertificatePath(envProvider, propProvider, certConfigPathOverride); - } - /** * 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 diff --git a/oauth2_http/java/com/google/auth/oauth2/IdentityPoolCredentials.java b/oauth2_http/java/com/google/auth/oauth2/IdentityPoolCredentials.java index afb6ebce9..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,16 +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(); + 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; diff --git a/oauth2_http/java/com/google/auth/oauth2/SystemEnvironmentProvider.java b/oauth2_http/java/com/google/auth/oauth2/SystemEnvironmentProvider.java index 75c5ed203..0f32a1ed1 100644 --- a/oauth2_http/java/com/google/auth/oauth2/SystemEnvironmentProvider.java +++ b/oauth2_http/java/com/google/auth/oauth2/SystemEnvironmentProvider.java @@ -1,5 +1,6 @@ package com.google.auth.oauth2; +import com.google.api.core.InternalApi; import java.io.Serializable; /** @@ -7,6 +8,7 @@ * *

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/javatests/com/google/auth/mtls/X509ProviderTest.java b/oauth2_http/javatests/com/google/auth/mtls/X509ProviderTest.java index bf71d7229..88037c2c2 100644 --- a/oauth2_http/javatests/com/google/auth/mtls/X509ProviderTest.java +++ b/oauth2_http/javatests/com/google/auth/mtls/X509ProviderTest.java @@ -60,11 +60,12 @@ class X509ProviderTest { void x509Provider_fileDoesntExist_throws() { String certConfigPath = "badfile.txt"; X509Provider testProvider = new X509Provider(certConfigPath); - String expectedErrorMessage = "File does not exist."; - + 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 diff --git a/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java index c9a19fbfd..28a19a8da 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java @@ -1120,7 +1120,6 @@ void build_withCertificateSourceAndCustomX509Provider_success() // Verify the custom provider methods were called during build. verify(x509Provider).getKeyStore(); - verify(x509Provider).getCertificatePath(); } @Test @@ -1182,15 +1181,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 = @@ -1198,10 +1199,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( @@ -1310,7 +1310,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(); @@ -1326,20 +1325,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; - } } } From 39707b085d61bc777cd359f80a6d12c5ae37e024 Mon Sep 17 00:00:00 2001 From: Pranav Iyer Date: Fri, 27 Mar 2026 16:15:34 -0400 Subject: [PATCH 9/9] Test fix. --- .../oauth2/SystemEnvironmentProvider.java | 30 +++++++++++++++++++ .../oauth2/IdentityPoolCredentialsTest.java | 24 +++++++-------- 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/oauth2_http/java/com/google/auth/oauth2/SystemEnvironmentProvider.java b/oauth2_http/java/com/google/auth/oauth2/SystemEnvironmentProvider.java index 0f32a1ed1..a58285831 100644 --- a/oauth2_http/java/com/google/auth/oauth2/SystemEnvironmentProvider.java +++ b/oauth2_http/java/com/google/auth/oauth2/SystemEnvironmentProvider.java @@ -1,3 +1,33 @@ +/* + * 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; diff --git a/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java index 28a19a8da..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; @@ -1079,14 +1077,8 @@ void build_withCertificateSource_succeeds() throws Exception { } @Test - void build_withCertificateSourceAndCustomX509Provider_success() + void build_withDefaultCertificateConfig_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")); - // Set up credential source for certificate type. Map certificateMap = new HashMap<>(); certificateMap.put("use_default_certificate_config", true); @@ -1097,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) @@ -1117,9 +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(); } @Test