3131
3232package com .google .auth .oauth2 ;
3333
34+ import static com .google .common .base .Preconditions .checkArgument ;
35+
3436import java .util .HashMap ;
3537import java .util .Locale ;
3638import java .util .Map ;
@@ -48,6 +50,157 @@ public class IdentityPoolCredentialSource extends ExternalAccountCredentials.Cre
4850 String credentialLocation ;
4951 @ Nullable String subjectTokenFieldName ;
5052 @ Nullable Map <String , String > headers ;
53+ @ Nullable CertificateConfig certificateConfig ;
54+
55+ /**
56+ * Extracts and configures the {@link CertificateConfig} from the provided credential source.
57+ *
58+ * @param credentialSourceMap A map containing the certificate configuration.
59+ * @return A new {@link CertificateConfig} instance.
60+ * @throws IllegalArgumentException if the 'certificate' entry is not a Map or if required fields
61+ * within the certificate configuration have invalid types.
62+ */
63+ private CertificateConfig getCertificateConfig (Map <String , Object > credentialSourceMap ) {
64+ Object certValue = credentialSourceMap .get ("certificate" );
65+ if (!(certValue instanceof Map )) {
66+ throw new IllegalArgumentException (
67+ "The 'certificate' credential source must be a JSON object (Map)." );
68+ }
69+ Map <String , Object > certificateMap = (Map <String , Object >) certValue ;
70+
71+ Boolean useDefaultCertificateConfig =
72+ getOptionalBoolean (certificateMap , "use_default_certificate_config" );
73+ String trustChain = getOptionalString (certificateMap , "trust_chain_path" );
74+ String certificateConfigLocation =
75+ getOptionalString (certificateMap , "certificate_config_location" );
76+
77+ return new CertificateConfig (
78+ useDefaultCertificateConfig , certificateConfigLocation , trustChain );
79+ }
80+
81+ /**
82+ * Retrieves an optional boolean value from a map.
83+ *
84+ * @param map The map to retrieve from.
85+ * @param key The key of the boolean value.
86+ * @return The boolean value if present and of the correct type, otherwise null.
87+ * @throws IllegalArgumentException if the value is present but not a boolean.
88+ */
89+ private @ Nullable Boolean getOptionalBoolean (Map <String , Object > map , String key ) {
90+ Object value = map .get (key );
91+ if (value == null ) {
92+ return null ;
93+ }
94+ if (!(value instanceof Boolean )) {
95+ throw new IllegalArgumentException (
96+ String .format (
97+ "Invalid type for '%s' in certificate configuration: expected Boolean, got %s." ,
98+ key , value .getClass ().getSimpleName ()));
99+ }
100+ return (Boolean ) value ;
101+ }
102+
103+ /**
104+ * Retrieves an optional string value from a map.
105+ *
106+ * @param map The map to retrieve from.
107+ * @param key The key of the string value.
108+ * @return The string value if present and of the correct type, otherwise null.
109+ * @throws IllegalArgumentException if the value is present but not a string.
110+ */
111+ private @ Nullable String getOptionalString (Map <String , Object > map , String key ) {
112+ Object value = map .get (key );
113+ if (value == null ) {
114+ return null ;
115+ }
116+ if (!(value instanceof String )) {
117+ throw new IllegalArgumentException (
118+ String .format (
119+ "Invalid type for '%s' in certificate configuration: expected String, got %s." ,
120+ key , value .getClass ().getSimpleName ()));
121+ }
122+ return (String ) value ;
123+ }
124+ /**
125+ * Represents the configuration options for X.509-based workload credentials (mTLS). It specifies
126+ * how to locate and use the client certificate, private key, and optional trust chain for mutual
127+ * TLS authentication.
128+ */
129+ public static class CertificateConfig implements java .io .Serializable {
130+ private static final long serialVersionUID = 1L ;
131+
132+ /**
133+ * If true, attempts to load the default certificate configuration. It checks the
134+ * GOOGLE_API_CERTIFICATE_CONFIG environment variable first, then a conventional default file
135+ * location. Cannot be true if {@code certificateConfigLocation} is set.
136+ */
137+ private final boolean useDefaultCertificateConfig ;
138+
139+ /**
140+ * Specifies the path to the client certificate and private key file. This is used when {@code
141+ * useDefaultCertificateConfig} is false or unset. Must be set if {@code
142+ * useDefaultCertificateConfig} is false.
143+ */
144+ @ Nullable private final String certificateConfigLocation ;
145+
146+ /**
147+ * Specifies the path to a PEM-formatted file containing the X.509 certificate trust chain. This
148+ * file should contain any intermediate certificates required to complete the trust chain
149+ * between the leaf certificate (used for mTLS) and the root certificate(s) in your workload
150+ * identity pool's trust store. The leaf certificate and any certificates already present in the
151+ * workload identity pool's trust store are optional in this file. Certificates should be
152+ * ordered with the leaf certificate (or the certificate which signed the leaf) first.
153+ */
154+ @ Nullable private final String trustChainPath ;
155+
156+ /**
157+ * Constructor for {@code CertificateConfig}.
158+ *
159+ * @param useDefaultCertificateConfig Whether to use the default certificate configuration.
160+ * @param certificateConfigLocation Path to the client certificate and private key file.
161+ * @param trustChainPath Path to the trust chain file.
162+ * @throws IllegalArgumentException if the configuration is invalid (e.g., neither default nor
163+ * location is specified, or both are specified).
164+ */
165+ CertificateConfig (
166+ @ Nullable Boolean useDefaultCertificateConfig ,
167+ @ Nullable String certificateConfigLocation ,
168+ @ Nullable String trustChainPath ) {
169+
170+ boolean useDefault = useDefaultCertificateConfig != null && useDefaultCertificateConfig ;
171+ boolean locationIsPresent =
172+ certificateConfigLocation != null && !certificateConfigLocation .isEmpty ();
173+
174+ checkArgument (
175+ !(!useDefault && !locationIsPresent ),
176+ "credentials: \" certificate\" object must either specify a certificate_config_location or use_default_certificate_config should be true" );
177+
178+ checkArgument (
179+ !(useDefault && locationIsPresent ),
180+ "credentials: \" certificate\" object cannot specify both a certificate_config_location and use_default_certificate_config=true" );
181+
182+ this .useDefaultCertificateConfig = useDefault ;
183+ this .certificateConfigLocation = certificateConfigLocation ;
184+ this .trustChainPath = trustChainPath ;
185+ }
186+
187+ /** Returns whether the default certificate configuration should be used. */
188+ public boolean useDefaultCertificateConfig () {
189+ return useDefaultCertificateConfig ;
190+ }
191+
192+ /** Returns the path to the client certificate file, or null if not set. */
193+ @ Nullable
194+ public String getCertificateConfigLocation () {
195+ return certificateConfigLocation ;
196+ }
197+
198+ /** Returns the path to the trust chain file, or null if not set. */
199+ @ Nullable
200+ public String getTrustChainPath () {
201+ return trustChainPath ;
202+ }
203+ }
51204
52205 /**
53206 * The source of the 3P credential.
@@ -69,9 +222,15 @@ public class IdentityPoolCredentialSource extends ExternalAccountCredentials.Cre
69222 public IdentityPoolCredentialSource (Map <String , Object > credentialSourceMap ) {
70223 super (credentialSourceMap );
71224
72- if (credentialSourceMap .containsKey ("file" ) && credentialSourceMap .containsKey ("url" )) {
225+ boolean filePresent = credentialSourceMap .containsKey ("file" );
226+ boolean urlPresent = credentialSourceMap .containsKey ("url" );
227+ boolean certificatePresent = credentialSourceMap .containsKey ("certificate" );
228+
229+ if ((filePresent && urlPresent )
230+ || (filePresent && certificatePresent )
231+ || (urlPresent && certificatePresent )) {
73232 throw new IllegalArgumentException (
74- "Only one credential source type can be set, either file or url ." );
233+ "Only one credential source type can be set: ' file', 'url', or 'certificate' ." );
75234 }
76235
77236 if (credentialSourceMap .containsKey ("file" )) {
@@ -80,6 +239,9 @@ public IdentityPoolCredentialSource(Map<String, Object> credentialSourceMap) {
80239 } else if (credentialSourceMap .containsKey ("url" )) {
81240 credentialLocation = (String ) credentialSourceMap .get ("url" );
82241 credentialSourceType = IdentityPoolCredentialSourceType .URL ;
242+ } else if (credentialSourceMap .containsKey ("certificate" )) {
243+ credentialSourceType = IdentityPoolCredentialSourceType .CERTIFICATE ;
244+ this .certificateConfig = getCertificateConfig (credentialSourceMap );
83245 } else {
84246 throw new IllegalArgumentException (
85247 "Missing credential source file location or URL. At least one must be specified." );
@@ -121,7 +283,8 @@ boolean hasHeaders() {
121283
122284 enum IdentityPoolCredentialSourceType {
123285 FILE ,
124- URL
286+ URL ,
287+ CERTIFICATE
125288 }
126289
127290 enum CredentialFormatType {
0 commit comments