Skip to content

Commit 3a4bae6

Browse files
committed
feat(mtls): Introduce DefaultMtlsProviderFactory and SecureConnectProvider
1 parent 4340684 commit 3a4bae6

File tree

8 files changed

+505
-1
lines changed

8 files changed

+505
-1
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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+
/** Data class representing context_aware_metadata.json file. */
39+
public class ContextAwareMetadataJson extends GenericJson {
40+
/** Cert provider command */
41+
@Key("cert_provider_command")
42+
private List<String> commands;
43+
44+
/** Returns the cert provider command. */
45+
public final ImmutableList<String> getCommands() {
46+
return ImmutableList.copyOf(commands);
47+
}
48+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright 2025, Google Inc. All rights reserved.
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+
*
15+
* * Neither the name of Google Inc. nor the names of its
16+
* contributors may be used to endorse or promote products derived from
17+
* this software without specific prior written permission.
18+
*
19+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21+
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22+
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23+
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24+
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25+
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26+
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27+
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30+
*/
31+
32+
package com.google.auth.mtls;
33+
34+
import com.google.auth.mtls.CertificateSourceUnavailableException;
35+
import com.google.auth.mtls.X509Provider;
36+
import java.io.IOException;
37+
38+
public class DefaultMtlsProviderFactory {
39+
40+
/**
41+
* Creates an instance of {@link MtlsProvider}. It first attempts to create an
42+
* {@link X509Provider}. If the certificate source is unavailable, it falls back to creating a
43+
* {@link SecureConnectProvider}. If the secure connect provider also fails, it throws the
44+
* original {@link CertificateSourceUnavailableException}.
45+
*
46+
* @return an instance of {@link MtlsProvider}.
47+
* @throws CertificateSourceUnavailableException if neither provider can be created.
48+
* @throws IOException if an I/O error occurs during provider creation.
49+
*/
50+
public static MtlsProvider create() throws IOException {
51+
MtlsProvider mtlsProvider;
52+
try {
53+
mtlsProvider = new X509Provider();
54+
mtlsProvider.getKeyStore();
55+
return mtlsProvider;
56+
} catch (CertificateSourceUnavailableException e) {
57+
try {
58+
mtlsProvider = new SecureConnectProvider();
59+
mtlsProvider.getKeyStore();
60+
return mtlsProvider;
61+
} catch (CertificateSourceUnavailableException ex) {
62+
throw new CertificateSourceUnavailableException(
63+
"No MtlsSource is available on this device.");
64+
}
65+
}
66+
}
67+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2025, Google Inc. All rights reserved.
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+
*
15+
* * Neither the name of Google Inc. nor the names of its
16+
* contributors may be used to endorse or promote products derived from
17+
* this software without specific prior written permission.
18+
*
19+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21+
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22+
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23+
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24+
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25+
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26+
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27+
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30+
*/
31+
32+
package com.google.auth.mtls;
33+
34+
import java.io.IOException;
35+
import java.security.KeyStore;
36+
37+
public interface MtlsProvider {
38+
/** Returns the mutual TLS key store. */
39+
KeyStore getKeyStore() throws IOException;
40+
}
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/*
2+
* Copyright 2025, Google Inc. All rights reserved.
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+
*
15+
* * Neither the name of Google Inc. nor the names of its
16+
* contributors may be used to endorse or promote products derived from
17+
* this software without specific prior written permission.
18+
*
19+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21+
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22+
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23+
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24+
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25+
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26+
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27+
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30+
*/
31+
32+
package com.google.auth.mtls;
33+
34+
import com.google.api.client.json.JsonParser;
35+
import com.google.api.client.json.gson.GsonFactory;
36+
import com.google.api.client.util.SecurityUtils;
37+
import com.google.common.annotations.VisibleForTesting;
38+
import com.google.common.collect.ImmutableList;
39+
import java.io.FileInputStream;
40+
import java.io.FileNotFoundException;
41+
import java.io.IOException;
42+
import java.io.InputStream;
43+
import java.security.GeneralSecurityException;
44+
import java.security.KeyStore;
45+
import java.util.List;
46+
47+
/**
48+
* Provider class for mutual TLS. It is used to configure the mutual TLS in the transport with the
49+
* default client certificate on device.
50+
*/
51+
public class SecureConnectProvider implements MtlsProvider{
52+
interface ProcessProvider {
53+
public Process createProcess(InputStream metadata) throws IOException;
54+
}
55+
56+
static class DefaultProcessProvider implements ProcessProvider {
57+
@Override
58+
public Process createProcess(InputStream metadata) throws IOException {
59+
if (metadata == null) {
60+
return null;
61+
}
62+
List<String> command = extractCertificateProviderCommand(metadata);
63+
return new ProcessBuilder(command).start();
64+
}
65+
}
66+
67+
private static final String DEFAULT_CONTEXT_AWARE_METADATA_PATH =
68+
System.getProperty("user.home") + "/.secureConnect/context_aware_metadata.json";
69+
70+
private String metadataPath;
71+
private ProcessProvider processProvider;
72+
73+
@VisibleForTesting
74+
SecureConnectProvider(ProcessProvider processProvider, String metadataPath) {
75+
this.processProvider = processProvider;
76+
this.metadataPath = metadataPath;
77+
}
78+
79+
public SecureConnectProvider() {
80+
this(new DefaultProcessProvider(), DEFAULT_CONTEXT_AWARE_METADATA_PATH);
81+
}
82+
83+
/** The mutual TLS key store created with the default client certificate on device. */
84+
@Override
85+
public KeyStore getKeyStore() throws IOException {
86+
try (InputStream stream = new FileInputStream(metadataPath)) {
87+
return getKeyStore(stream, processProvider);
88+
} catch (InterruptedException e) {
89+
throw new IOException("Interrupted executing certificate provider command", e);
90+
} catch (GeneralSecurityException e) {
91+
throw new CertificateSourceUnavailableException("SecureConnect encountered GeneralSecurityException:", e);
92+
} catch (FileNotFoundException exception) {
93+
// If the metadata file doesn't exist, then there is no key store, so we will throw sentinel error
94+
throw new CertificateSourceUnavailableException("SecureConnect metadata does not exist.");
95+
}
96+
}
97+
98+
@VisibleForTesting
99+
static KeyStore getKeyStore(InputStream metadata, ProcessProvider processProvider)
100+
throws IOException, InterruptedException, GeneralSecurityException {
101+
Process process = processProvider.createProcess(metadata);
102+
103+
// Run the command and timeout after 1000 milliseconds.
104+
int exitCode = runCertificateProviderCommand(process, 1000);
105+
if (exitCode != 0) {
106+
throw new IOException("Cert provider command failed with exit code: " + exitCode);
107+
}
108+
109+
// Create mTLS key store with the input certificates from shell command.
110+
return SecurityUtils.createMtlsKeyStore(process.getInputStream());
111+
}
112+
113+
@VisibleForTesting
114+
static ImmutableList<String> extractCertificateProviderCommand(InputStream contextAwareMetadata)
115+
throws IOException {
116+
JsonParser parser = new GsonFactory().createJsonParser(contextAwareMetadata);
117+
ContextAwareMetadataJson json = parser.parse(ContextAwareMetadataJson.class);
118+
return json.getCommands();
119+
}
120+
121+
@VisibleForTesting
122+
static int runCertificateProviderCommand(Process commandProcess, long timeoutMilliseconds)
123+
throws IOException, InterruptedException {
124+
long startTime = System.currentTimeMillis();
125+
long remainTime = timeoutMilliseconds;
126+
127+
// In the while loop, keep checking if the process is terminated every 100 milliseconds
128+
// until timeout is reached or process is terminated. In getKeyStore we set timeout to
129+
// 1000 milliseconds, so 100 millisecond is a good number for the sleep.
130+
while (remainTime > 0) {
131+
Thread.sleep(Math.min(remainTime + 1, 100));
132+
remainTime -= System.currentTimeMillis() - startTime;
133+
134+
try {
135+
return commandProcess.exitValue();
136+
} catch (IllegalThreadStateException ignored) {
137+
// exitValue throws IllegalThreadStateException if process has not yet terminated.
138+
// Once the process is terminated, exitValue no longer throws exception. Therefore
139+
// in the while loop, we use exitValue to check if process is terminated. See
140+
// https://docs.oracle.com/javase/7/docs/api/java/lang/Process.html#exitValue()
141+
// for more details.
142+
}
143+
}
144+
145+
commandProcess.destroy();
146+
throw new IOException("cert provider command timed out");
147+
}
148+
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
* libraries, and the public facing methods may be changed without notice, and have no guarantee of
4949
* backwards compatability.
5050
*/
51-
public class X509Provider {
51+
public class X509Provider implements MtlsProvider{
5252
static final String CERTIFICATE_CONFIGURATION_ENV_VARIABLE = "GOOGLE_API_CERTIFICATE_CONFIG";
5353
static final String WELL_KNOWN_CERTIFICATE_CONFIG_FILE = "certificate_config.json";
5454
static final String CLOUDSDK_CONFIG_DIRECTORY = "gcloud";
@@ -89,6 +89,7 @@ public X509Provider() {
8989
* @return a KeyStore containing the X.509 certificate specified by the certificate configuration.
9090
* @throws IOException if there is an error retrieving the certificate configuration.
9191
*/
92+
@Override
9293
public KeyStore getKeyStore() throws IOException {
9394

9495
WorkloadCertificateConfiguration workloadCertConfig = getWorkloadCertificateConfiguration();

0 commit comments

Comments
 (0)