Skip to content

Commit b7903d0

Browse files
committed
Responding to PR comments and adding a new exception type.
1 parent b46d2a1 commit b7903d0

File tree

5 files changed

+152
-27
lines changed

5 files changed

+152
-27
lines changed
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
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+
36+
/**
37+
* This exception is thrown by certificate providers in the Google auth library when the certificate
38+
* source is unavailable. This means that the transport layer should move on to the next certificate
39+
* source provider type.
40+
*/
41+
public class CertificateSourceUnavailableException extends IOException {
42+
43+
/**
44+
* Constructor with a message and throwable cause.
45+
*
46+
* @param message The detail message (which is saved for later retrieval by the {@link
47+
* #getMessage()} method)
48+
* @param cause The cause (which is saved for later retrieval by the {@link #getCause()} method).
49+
* (A null value is permitted, and indicates that the cause is nonexistent or unknown.)
50+
*/
51+
public CertificateSourceUnavailableException(String message, Throwable cause) {
52+
super(message, cause);
53+
}
54+
55+
/**
56+
* Constructor with a throwable cause.
57+
*
58+
* @param cause The cause (which is saved for later retrieval by the {@link #getCause()} method).
59+
* (A null value is permitted, and indicates that the cause is nonexistent or unknown.)
60+
*/
61+
public CertificateSourceUnavailableException(Throwable cause) {
62+
super(cause);
63+
}
64+
65+
/**
66+
* Constructor with a message.
67+
*
68+
* @param message The detail message (which is saved for later retrieval by the {@link
69+
* #getMessage()} method)
70+
*/
71+
public CertificateSourceUnavailableException(String message) {
72+
super(message);
73+
}
74+
}

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import com.google.api.client.json.JsonFactory;
3636
import com.google.api.client.json.JsonObjectParser;
3737
import com.google.api.client.json.gson.GsonFactory;
38+
import com.google.common.base.Preconditions;
3839
import com.google.common.base.Strings;
3940
import java.io.IOException;
4041
import java.io.InputStream;
@@ -64,9 +65,7 @@ String getPrivateKeyPath() {
6465

6566
static WorkloadCertificateConfiguration fromCertificateConfigurationStream(
6667
InputStream certConfigStream) throws IOException {
67-
if (certConfigStream == null) {
68-
throw new IllegalArgumentException("certConfigStream must not be null.");
69-
}
68+
Preconditions.checkNotNull(certConfigStream);
7069

7170
GenericJson fileContents =
7271
parser.parseAndClose(certConfigStream, StandardCharsets.UTF_8, GenericJson.class);
@@ -79,7 +78,7 @@ static WorkloadCertificateConfiguration fromCertificateConfigurationStream(
7978

8079
Map<String, Object> workloadConfig = (Map<String, Object>) certConfigs.get("workload");
8180
if (workloadConfig == null) {
82-
throw new IllegalArgumentException(
81+
throw new CertificateSourceUnavailableException(
8382
"A workload certificate configuration must be provided in the cert_configs object.");
8483
}
8584

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

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
package com.google.auth.mtls;
3333

3434
import com.google.api.client.util.SecurityUtils;
35+
import com.google.common.base.Strings;
3536
import java.io.File;
3637
import java.io.FileInputStream;
3738
import java.io.FileNotFoundException;
@@ -41,17 +42,35 @@
4142
import java.security.KeyStore;
4243
import java.util.Locale;
4344

45+
/**
46+
* This class provides certificate key stores to the Google Auth library transport layer via
47+
* certificate configuration files. This is only meant to be used internally to Google Cloud
48+
* libraries, and the public facing methods may be changed without notice, and have no guarantee of
49+
* backwards compatability.
50+
*/
4451
public class X509Provider {
4552
static final String CERTIFICATE_CONFIGURATION_ENV_VARIABLE = "GOOGLE_API_CERTIFICATE_CONFIG";
4653
static final String WELL_KNOWN_CERTIFICATE_CONFIG_FILE = "certificate_config.json";
4754
static final String CLOUDSDK_CONFIG_DIRECTORY = "gcloud";
4855

4956
private String certConfigPathOverride;
5057

58+
/**
59+
* Creates an X509 provider with an override path for the certificate configuration, bypassing the
60+
* normal checks for the well known certificate configuration file path and environment variable.
61+
* This is meant for internal Google Cloud usage and behavior may be changed without warning.
62+
*
63+
* @param certConfigPathOverride the path to read the certificate configuration from.
64+
*/
5165
public X509Provider(String certConfigPathOverride) {
5266
this.certConfigPathOverride = certConfigPathOverride;
5367
}
5468

69+
/**
70+
* Creates a new X.509 provider that will check the environment variable path and the well known
71+
* Gcloud certificate configuration location. This is meant for internal Google Cloud usage and
72+
* behavior may be changed without warning.
73+
*/
5574
public X509Provider() {
5675
this(null);
5776
}
@@ -76,20 +95,24 @@ public KeyStore getKeyStore() throws IOException {
7695

7796
InputStream certStream = null;
7897
InputStream privateKeyStream = null;
98+
SequenceInputStream certAndPrivateKeyStream = null;
7999
try {
80100
// Read the certificate and private key file paths into separate streams.
81101
File certFile = new File(workloadCertConfig.getCertPath());
82102
File privateKeyFile = new File(workloadCertConfig.getPrivateKeyPath());
83-
certStream = readStream(certFile);
84-
privateKeyStream = readStream(privateKeyFile);
103+
certStream = createInputStream(certFile);
104+
privateKeyStream = createInputStream(privateKeyFile);
85105

86106
// Merge the two streams into a single stream.
87-
SequenceInputStream certAndPrivateKeyStream =
88-
new SequenceInputStream(certStream, privateKeyStream);
107+
certAndPrivateKeyStream = new SequenceInputStream(certStream, privateKeyStream);
89108

90109
// Build a key store using the combined stream.
91110
return SecurityUtils.createMtlsKeyStore(certAndPrivateKeyStream);
111+
} catch (CertificateSourceUnavailableException e) {
112+
// Throw the CertificateSourceUnavailableException without wrapping.
113+
throw e;
92114
} catch (Exception e) {
115+
// Wrap all other exception types to an IOException.
93116
throw new IOException(e);
94117
} finally {
95118
if (certStream != null) {
@@ -98,6 +121,9 @@ public KeyStore getKeyStore() throws IOException {
98121
if (privateKeyStream != null) {
99122
privateKeyStream.close();
100123
}
124+
if (certAndPrivateKeyStream != null) {
125+
certAndPrivateKeyStream.close();
126+
}
101127
}
102128
}
103129

@@ -108,7 +134,7 @@ private WorkloadCertificateConfiguration getWorkloadCertificateConfiguration()
108134
certConfig = new File(certConfigPathOverride);
109135
} else {
110136
String envCredentialsPath = getEnv(CERTIFICATE_CONFIGURATION_ENV_VARIABLE);
111-
if (envCredentialsPath != null && !envCredentialsPath.isEmpty()) {
137+
if (!Strings.isNullOrEmpty(envCredentialsPath)) {
112138
certConfig = new File(envCredentialsPath);
113139
} else {
114140
certConfig = getWellKnownCertificateConfigFile();
@@ -120,13 +146,13 @@ private WorkloadCertificateConfiguration getWorkloadCertificateConfiguration()
120146
// Path will be put in the message from the catch block below
121147
throw new IOException("File does not exist.");
122148
}
123-
certConfigStream = readStream(certConfig);
149+
certConfigStream = createInputStream(certConfig);
124150
return WorkloadCertificateConfiguration.fromCertificateConfigurationStream(certConfigStream);
125151
} catch (Exception e) {
126152
// Although it is also the cause, the message of the caught exception can have very
127153
// important information for diagnosing errors, so include its message in the
128154
// outer exception message also.
129-
throw new IOException(
155+
throw new CertificateSourceUnavailableException(
130156
String.format(
131157
"Error reading certificate configuration file value '%s': %s",
132158
certConfig.getPath(), e.getMessage()),
@@ -145,7 +171,7 @@ boolean isFile(File file) {
145171
return file.isFile();
146172
}
147173

148-
InputStream readStream(File file) throws FileNotFoundException {
174+
InputStream createInputStream(File file) throws FileNotFoundException {
149175
return new FileInputStream(file);
150176
}
151177

oauth2_http/javatests/com/google/auth/mtls/WorkloadCertificateConfigurationTest.java

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,8 @@
3131

3232
package com.google.auth.mtls;
3333

34-
import static org.junit.Assert.assertEquals;
3534
import static org.junit.Assert.assertNotNull;
3635
import static org.junit.Assert.assertTrue;
37-
import static org.junit.Assert.fail;
3836

3937
import com.google.api.client.json.GenericJson;
4038
import com.google.auth.TestUtils;
@@ -63,8 +61,15 @@ public void workloadCertificateConfig_fromStreamMissingCertPath_Fails() throws I
6361
InputStream configStream = writeWorkloadCertificateConfigStream(certPath, privateKeyPath);
6462

6563
IllegalArgumentException exception =
66-
Assert.assertThrows(IllegalArgumentException.class, () -> WorkloadCertificateConfiguration.fromCertificateConfigurationStream(configStream));
67-
assertTrue(exception.getMessage().contains("The cert_path field must be provided in the workload certificate configuration."));
64+
Assert.assertThrows(
65+
IllegalArgumentException.class,
66+
() ->
67+
WorkloadCertificateConfiguration.fromCertificateConfigurationStream(configStream));
68+
assertTrue(
69+
exception
70+
.getMessage()
71+
.contains(
72+
"The cert_path field must be provided in the workload certificate configuration."));
6873
}
6974

7075
@Test
@@ -74,8 +79,15 @@ public void workloadCertificateConfig_fromStreamMissingPrivateKeyPath_Fails() th
7479
InputStream configStream = writeWorkloadCertificateConfigStream(certPath, privateKeyPath);
7580

7681
IllegalArgumentException exception =
77-
Assert.assertThrows(IllegalArgumentException.class, () -> WorkloadCertificateConfiguration.fromCertificateConfigurationStream(configStream));
78-
assertTrue(exception.getMessage().contains("The key_path field must be provided in the workload certificate configuration."));
82+
Assert.assertThrows(
83+
IllegalArgumentException.class,
84+
() ->
85+
WorkloadCertificateConfiguration.fromCertificateConfigurationStream(configStream));
86+
assertTrue(
87+
exception
88+
.getMessage()
89+
.contains(
90+
"The key_path field must be provided in the workload certificate configuration."));
7991
}
8092

8193
@Test
@@ -84,9 +96,16 @@ public void workloadCertificateConfig_fromStreamMissingWorkload_Fails() throws I
8496
json.put("cert_configs", new GenericJson());
8597
InputStream configStream = TestUtils.jsonToInputStream(json);
8698

87-
IllegalArgumentException exception =
88-
Assert.assertThrows(IllegalArgumentException.class, () -> WorkloadCertificateConfiguration.fromCertificateConfigurationStream(configStream));
89-
assertTrue(exception.getMessage().contains("A workload certificate configuration must be provided in the cert_configs object."));
99+
CertificateSourceUnavailableException exception =
100+
Assert.assertThrows(
101+
CertificateSourceUnavailableException.class,
102+
() ->
103+
WorkloadCertificateConfiguration.fromCertificateConfigurationStream(configStream));
104+
assertTrue(
105+
exception
106+
.getMessage()
107+
.contains(
108+
"A workload certificate configuration must be provided in the cert_configs object."));
90109
}
91110

92111
@Test
@@ -95,8 +114,15 @@ public void workloadCertificateConfig_fromStreamMissingCertConfig_Fails() throws
95114
InputStream configStream = TestUtils.jsonToInputStream(json);
96115

97116
IllegalArgumentException exception =
98-
Assert.assertThrows(IllegalArgumentException.class, () -> WorkloadCertificateConfiguration.fromCertificateConfigurationStream(configStream));
99-
assertTrue(exception.getMessage().contains("The cert_configs object must be provided in the certificate configuration file."));
117+
Assert.assertThrows(
118+
IllegalArgumentException.class,
119+
() ->
120+
WorkloadCertificateConfiguration.fromCertificateConfigurationStream(configStream));
121+
assertTrue(
122+
exception
123+
.getMessage()
124+
.contains(
125+
"The cert_configs object must be provided in the certificate configuration file."));
100126
}
101127

102128
static InputStream writeWorkloadCertificateConfigStream(String certPath, String privateKeyPath)

oauth2_http/javatests/com/google/auth/mtls/X509ProviderTest.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
package com.google.auth.mtls;
3333

3434
import static org.junit.Assert.assertTrue;
35-
import static org.junit.Assert.fail;
3635

3736
import java.io.ByteArrayInputStream;
3837
import java.io.File;
@@ -94,8 +93,9 @@ public void x509Provider_fileDoesntExist_throws() {
9493
"Error reading certificate configuration file value '%s': File does not exist.",
9594
certConfigPath);
9695

97-
IOException exception =
98-
Assert.assertThrows(IOException.class, () -> testProvider.getKeyStore());
96+
CertificateSourceUnavailableException exception =
97+
Assert.assertThrows(
98+
CertificateSourceUnavailableException.class, () -> testProvider.getKeyStore());
9999
assertTrue(exception.getMessage().contains(expectedErrorMessage));
100100
}
101101

@@ -236,7 +236,7 @@ boolean isFile(File file) {
236236
}
237237

238238
@Override
239-
InputStream readStream(File file) throws FileNotFoundException {
239+
InputStream createInputStream(File file) throws FileNotFoundException {
240240
InputStream stream = files.get(file.getPath());
241241
if (stream == null) {
242242
throw new FileNotFoundException(file.getPath());

0 commit comments

Comments
 (0)