Skip to content

Commit c9fd1b1

Browse files
authored
feat(mtls): Introduce DefaultMtlsProviderFactory and SecureConnectProvider (#1730)
This PR allows the Java auth lib to provide either an X509 mTLS provider or SecureConnect mTLS provider based on availability and under a common "MtlsProvider" interface. The Java GAX lib will be refactored to use this factory and interface in a subsequent PR.
1 parent 153fa4a commit c9fd1b1

File tree

10 files changed

+595
-43
lines changed

10 files changed

+595
-43
lines changed

oauth2_http/java/com/google/auth/mtls/CertificateSourceUnavailableException.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
11
/*
2-
* Copyright 2025, Google Inc. All rights reserved.
2+
* Copyright 2025 Google LLC
33
*
44
* Redistribution and use in source and binary forms, with or without
55
* modification, are permitted provided that the following conditions are
66
* met:
77
*
8-
* * Redistributions of source code must retain the above copyright
8+
* * Redistributions of source code must retain the above copyright
99
* notice, this list of conditions and the following disclaimer.
10-
* * Redistributions in binary form must reproduce the above
10+
* * Redistributions in binary form must reproduce the above
1111
* copyright notice, this list of conditions and the following disclaimer
1212
* in the documentation and/or other materials provided with the
1313
* distribution.
14-
*
15-
* * Neither the name of Google Inc. nor the names of its
14+
* * Neither the name of Google LLC nor the names of its
1615
* contributors may be used to endorse or promote products derived from
1716
* this software without specific prior written permission.
1817
*
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Redistribution and use in source and binary forms, with or without
5+
* modification, are permitted provided that the following conditions are
6+
* met:
7+
*
8+
* * Redistributions of source code must retain the above copyright
9+
* notice, this list of conditions and the following disclaimer.
10+
* * Redistributions in binary form must reproduce the above
11+
* copyright notice, this list of conditions and the following disclaimer
12+
* in the documentation and/or other materials provided with the
13+
* distribution.
14+
* * Neither the name of Google LLC nor the names of its
15+
* contributors may be used to endorse or promote products derived from
16+
* this software without specific prior written permission.
17+
*
18+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20+
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21+
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22+
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23+
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24+
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25+
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26+
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29+
*/
30+
31+
package com.google.auth.mtls;
32+
33+
import com.google.api.client.json.GenericJson;
34+
import com.google.api.client.util.Key;
35+
import com.google.common.collect.ImmutableList;
36+
import java.util.List;
37+
38+
/**
39+
* Data class representing context_aware_metadata.json file. This is meant for internal Google Cloud
40+
* usage and behavior may be changed without warning.
41+
*
42+
* <p>Note: This implementation is duplicated from the existing ContextAwareMetadataJson found in
43+
* the Gax library. The Gax library version of ContextAwareMetadataJson will be marked as deprecated
44+
* in the future.
45+
*/
46+
public class ContextAwareMetadataJson extends GenericJson {
47+
/** Cert provider command */
48+
@Key("cert_provider_command")
49+
private List<String> commands;
50+
51+
/** Returns the cert provider command. */
52+
public final ImmutableList<String> getCommands() {
53+
return ImmutableList.copyOf(commands);
54+
}
55+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Redistribution and use in source and binary forms, with or without
5+
* modification, are permitted provided that the following conditions are
6+
* met:
7+
*
8+
* * Redistributions of source code must retain the above copyright
9+
* notice, this list of conditions and the following disclaimer.
10+
* * Redistributions in binary form must reproduce the above
11+
* copyright notice, this list of conditions and the following disclaimer
12+
* in the documentation and/or other materials provided with the
13+
* distribution.
14+
* * Neither the name of Google LLC nor the names of its
15+
* contributors may be used to endorse or promote products derived from
16+
* this software without specific prior written permission.
17+
*
18+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20+
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21+
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22+
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23+
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24+
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25+
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26+
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29+
*/
30+
31+
package com.google.auth.mtls;
32+
33+
import java.io.IOException;
34+
35+
public class DefaultMtlsProviderFactory {
36+
37+
/**
38+
* Creates an instance of {@link MtlsProvider}. It first attempts to create an {@link
39+
* com.google.auth.mtls.X509Provider}. If the certificate source is unavailable, it falls back to
40+
* creating a {@link SecureConnectProvider}. If the secure connect provider also fails, it throws
41+
* a {@link com.google.auth.mtls.CertificateSourceUnavailableException}.
42+
*
43+
* <p>This is only meant to be used internally by Google Cloud libraries, and the public facing
44+
* methods may be changed without notice, and have no guarantee of backwards compatibility.
45+
*
46+
* @return an instance of {@link MtlsProvider}.
47+
* @throws com.google.auth.mtls.CertificateSourceUnavailableException if neither provider can be
48+
* created.
49+
* @throws IOException if an I/O error occurs during provider creation.
50+
*/
51+
public static MtlsProvider create() throws IOException {
52+
// Note: The caller should handle CertificateSourceUnavailableException gracefully, since
53+
// it is an expected error case. All other IOExceptions are unexpected and should be surfaced
54+
// up the call stack.
55+
MtlsProvider mtlsProvider = new X509Provider();
56+
if (mtlsProvider.isAvailable()) {
57+
return mtlsProvider;
58+
}
59+
mtlsProvider = new SecureConnectProvider();
60+
if (mtlsProvider.isAvailable()) {
61+
return mtlsProvider;
62+
}
63+
throw new CertificateSourceUnavailableException(
64+
"No Certificate Source is available on this device.");
65+
}
66+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Redistribution and use in source and binary forms, with or without
5+
* modification, are permitted provided that the following conditions are
6+
* met:
7+
*
8+
* * Redistributions of source code must retain the above copyright
9+
* notice, this list of conditions and the following disclaimer.
10+
* * Redistributions in binary form must reproduce the above
11+
* copyright notice, this list of conditions and the following disclaimer
12+
* in the documentation and/or other materials provided with the
13+
* distribution.
14+
* * Neither the name of Google LLC nor the names of its
15+
* contributors may be used to endorse or promote products derived from
16+
* this software without specific prior written permission.
17+
*
18+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20+
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21+
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22+
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23+
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24+
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25+
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26+
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29+
*/
30+
31+
package com.google.auth.mtls;
32+
33+
import java.io.IOException;
34+
import java.security.KeyStore;
35+
36+
/**
37+
* MtlsProvider is used by the Gax library for configuring mutual TLS in the HTTP and GRPC transport
38+
* layer. The source of the client certificate is up to the implementation.
39+
*
40+
* <p>Note: This interface will replace the identically named "MtlsProvider" implementation in the
41+
* Gax library. The Gax library version of MtlsProvider will be marked as deprecated. See
42+
* https://github.com/googleapis/google-auth-library-java/issues/1758
43+
*/
44+
public interface MtlsProvider {
45+
/**
46+
* Returns a mutual TLS key store.
47+
*
48+
* @return KeyStore for configuring mTLS.
49+
* @throws CertificateSourceUnavailableException if the certificate source is unavailable (ex.
50+
* missing configuration file).
51+
* @throws IOException if a general I/O error occurs while creating the KeyStore
52+
*/
53+
KeyStore getKeyStore() throws CertificateSourceUnavailableException, IOException;
54+
55+
/**
56+
* Returns true if the underlying mTLS provider is available.
57+
*
58+
* @throws IOException if a general I/O error occurs while determining availability.
59+
*/
60+
boolean isAvailable() throws IOException;
61+
}
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Redistribution and use in source and binary forms, with or without
5+
* modification, are permitted provided that the following conditions are
6+
* met:
7+
*
8+
* * Redistributions of source code must retain the above copyright
9+
* notice, this list of conditions and the following disclaimer.
10+
* * Redistributions in binary form must reproduce the above
11+
* copyright notice, this list of conditions and the following disclaimer
12+
* in the documentation and/or other materials provided with the
13+
* distribution.
14+
* * Neither the name of Google LLC nor the names of its
15+
* contributors may be used to endorse or promote products derived from
16+
* this software without specific prior written permission.
17+
*
18+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20+
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21+
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22+
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23+
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24+
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25+
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26+
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29+
*/
30+
31+
package com.google.auth.mtls;
32+
33+
import com.google.api.client.json.JsonParser;
34+
import com.google.api.client.json.gson.GsonFactory;
35+
import com.google.api.client.util.SecurityUtils;
36+
import com.google.common.annotations.VisibleForTesting;
37+
import com.google.common.collect.ImmutableList;
38+
import java.io.FileInputStream;
39+
import java.io.FileNotFoundException;
40+
import java.io.IOException;
41+
import java.io.InputStream;
42+
import java.security.GeneralSecurityException;
43+
import java.security.KeyStore;
44+
import java.util.List;
45+
import java.util.concurrent.TimeUnit;
46+
47+
/**
48+
* This class implements {@link MtlsProvider} for the Google Auth library transport layer via {@link
49+
* ContextAwareMetadataJson}. This is only meant to be used internally by Google Cloud libraries,
50+
* and the public facing methods may be changed without notice, and have no guarantee of backwards
51+
* compatibility.
52+
*
53+
* <p>Note: This implementation is derived from the existing "MtlsProvider" found in the Gax
54+
* library, with two notable differences: 1) All logic associated with parsing environment variables
55+
* related to "mTLS usage" are omitted - a separate helper class will be introduced in the Gax
56+
* library to serve this purpose. 2) getKeyStore throws {@link
57+
* com.google.auth.mtls.CertificateSourceUnavailableException} instead of returning "null" if this
58+
* cert source is not available on the device.
59+
*
60+
* <p>Additionally, this implementation will replace the existing "MtlsProvider" in the Gax library.
61+
* The Gax library version of MtlsProvider will be marked as deprecated.
62+
*/
63+
public class SecureConnectProvider implements MtlsProvider {
64+
interface ProcessProvider {
65+
public Process createProcess(InputStream metadata) throws IOException;
66+
}
67+
68+
static class DefaultProcessProvider implements ProcessProvider {
69+
@Override
70+
public Process createProcess(InputStream metadata) throws IOException {
71+
if (metadata == null) {
72+
throw new IOException("Error creating Process: metadata is null");
73+
}
74+
List<String> command = extractCertificateProviderCommand(metadata);
75+
return new ProcessBuilder(command).start();
76+
}
77+
}
78+
79+
private static final String DEFAULT_CONTEXT_AWARE_METADATA_PATH =
80+
System.getProperty("user.home") + "/.secureConnect/context_aware_metadata.json";
81+
82+
private String metadataPath;
83+
private ProcessProvider processProvider;
84+
85+
@VisibleForTesting
86+
SecureConnectProvider(ProcessProvider processProvider, String metadataPath) {
87+
this.processProvider = processProvider;
88+
this.metadataPath = metadataPath;
89+
}
90+
91+
public SecureConnectProvider() {
92+
this(new DefaultProcessProvider(), DEFAULT_CONTEXT_AWARE_METADATA_PATH);
93+
}
94+
95+
/**
96+
* Returns a mutual TLS key store backed by the certificate provided by the SecureConnect tool.
97+
*
98+
* @return a KeyStore containing the certificate provided by the SecureConnect tool.
99+
* @throws CertificateSourceUnavailableException if the certificate source is unavailable (ex.
100+
* missing configuration file).
101+
* @throws IOException if a general I/O error occurs while creating the KeyStore.
102+
*/
103+
@Override
104+
public KeyStore getKeyStore() throws CertificateSourceUnavailableException, IOException {
105+
try (InputStream stream = new FileInputStream(metadataPath)) {
106+
return getKeyStore(stream, processProvider);
107+
} catch (InterruptedException e) {
108+
throw new IOException("SecureConnect: Interrupted executing certificate provider command", e);
109+
} catch (GeneralSecurityException e) {
110+
throw new CertificateSourceUnavailableException(
111+
"SecureConnect encountered GeneralSecurityException:", e);
112+
} catch (FileNotFoundException exception) {
113+
// If the metadata file doesn't exist, then there is no key store, so we will throw sentinel
114+
// error
115+
throw new CertificateSourceUnavailableException("SecureConnect metadata does not exist.");
116+
}
117+
}
118+
119+
/**
120+
* Returns true if the SecureConnect mTLS provider is available.
121+
*
122+
* @throws IOException if a general I/O error occurs while determining availability.
123+
*/
124+
@Override
125+
public boolean isAvailable() throws IOException {
126+
try {
127+
this.getKeyStore();
128+
} catch (CertificateSourceUnavailableException e) {
129+
return false;
130+
}
131+
return true;
132+
}
133+
134+
@VisibleForTesting
135+
static KeyStore getKeyStore(InputStream metadata, ProcessProvider processProvider)
136+
throws IOException, InterruptedException, GeneralSecurityException {
137+
Process process = processProvider.createProcess(metadata);
138+
139+
// Run the command and timeout after 1000 milliseconds.
140+
// The cert provider command usually finishes instantly (if it doesn't hang),
141+
// so 1000 milliseconds is plenty of time.
142+
int exitCode = runCertificateProviderCommand(process, 1000);
143+
if (exitCode != 0) {
144+
throw new IOException(
145+
"SecureConnect: Cert provider command failed with exit code: " + exitCode);
146+
}
147+
148+
// Create mTLS key store with the input certificates from shell command.
149+
return SecurityUtils.createMtlsKeyStore(process.getInputStream());
150+
}
151+
152+
@VisibleForTesting
153+
static ImmutableList<String> extractCertificateProviderCommand(InputStream contextAwareMetadata)
154+
throws IOException {
155+
JsonParser parser = new GsonFactory().createJsonParser(contextAwareMetadata);
156+
ContextAwareMetadataJson json = parser.parse(ContextAwareMetadataJson.class);
157+
return json.getCommands();
158+
}
159+
160+
@VisibleForTesting
161+
static int runCertificateProviderCommand(Process commandProcess, long timeoutMilliseconds)
162+
throws IOException, InterruptedException {
163+
boolean terminated = commandProcess.waitFor(timeoutMilliseconds, TimeUnit.MILLISECONDS);
164+
if (!terminated) {
165+
commandProcess.destroy();
166+
throw new IOException("SecureConnect: Cert provider command timed out");
167+
}
168+
return commandProcess.exitValue();
169+
}
170+
}

0 commit comments

Comments
 (0)