Skip to content

Commit 2a57fb7

Browse files
committed
Refactor: move the trust chain logic into a helper private method.
1 parent 6996ec1 commit 2a57fb7

File tree

2 files changed

+66
-40
lines changed

2 files changed

+66
-40
lines changed

oauth2_http/java/com/google/auth/oauth2/CertificateIdentityPoolSubjectTokenSupplier.java

Lines changed: 58 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import java.io.ByteArrayInputStream;
3939
import java.io.IOException;
4040
import java.io.InputStream;
41+
import java.nio.charset.StandardCharsets;
4142
import java.nio.file.Files;
4243
import java.nio.file.NoSuchFileException;
4344
import java.nio.file.Paths;
@@ -84,7 +85,6 @@ private static String loadAndEncodeLeafCertificate(String path) throws IOExcepti
8485
} catch (NoSuchFileException e) {
8586
throw new IOException(String.format("Leaf certificate file not found: %s", path), e);
8687
} catch (CertificateException e) {
87-
// This catches parsing errors if the leaf certificate file is invalid.
8888
throw new IOException(
8989
String.format("Failed to parse leaf certificate from file: %s", path), e);
9090
} catch (IOException e) {
@@ -145,7 +145,7 @@ public String getSubjectToken(ExternalAccountSupplierContext context) throws IOE
145145
// Initialize the certificate chain for the subject token. The Security Token Service (STS)
146146
// requires that the leaf certificate (the one used for authenticating this workload) must be
147147
// the first certificate in this chain.
148-
java.util.List<String> certChain = new java.util.ArrayList<>();
148+
List<String> certChain = new ArrayList<>();
149149
certChain.add(encodedLeafCert);
150150

151151
// Handle trust chain loading and processing.
@@ -155,47 +155,14 @@ public String getSubjectToken(ExternalAccountSupplierContext context) throws IOE
155155

156156
// Process the trust chain certificates read from the file.
157157
if (!trustChainCerts.isEmpty()) {
158-
// Get the first certificate from the user-provided trust chain file.
159-
X509Certificate firstTrustCert = trustChainCerts.get(0);
160-
String encodedFirstTrustCert = encodeCert(firstTrustCert);
161-
162-
// If the first certificate in the user-provided trust chain file is *not* the leaf
163-
// certificate (which has already been added as the first element to `certChain`), then add
164-
// this certificate to `certChain`. This handles cases where the user's trust chain file
165-
// starts with an intermediate certificate. If the first certificate in the trust chain file
166-
// *is* the leaf certificate, this means the user has explicitly included the leaf in their
167-
// trust chain file. In this case, we skip adding it again to prevent duplication, as the
168-
// leaf is already at the beginning of `certChain`.
169-
if (!encodedFirstTrustCert.equals(encodedLeafCert)) {
170-
certChain.add(encodedFirstTrustCert);
171-
}
172-
173-
// Iterate over the remaining certificates in the trust chain.
174-
for (int i = 1; i < trustChainCerts.size(); i++) {
175-
X509Certificate currentCert = trustChainCerts.get(i);
176-
String encodedCurrentCert = encodeCert(currentCert);
177-
178-
// Throw an error if the current certificate (from the user-provided trust chain file,
179-
// at an index beyond the first) is the same as the leaf certificate.
180-
// This enforces that if the leaf certificate is included in the trust chain file by the
181-
// user, it must be the very first certificate in that file. It should not appear
182-
// elsewhere in the chain.
183-
if (encodedCurrentCert.equals(encodedLeafCert)) {
184-
throw new IllegalArgumentException(
185-
"The leaf certificate should only appear at the beginning of the trust chain file, or be omitted entirely.");
186-
}
187-
188-
// Add the current certificate to the chain.
189-
certChain.add(encodedCurrentCert);
190-
}
158+
populateCertChainFromTrustChain(certChain, trustChainCerts, encodedLeafCert);
191159
}
192160
} catch (IllegalArgumentException e) {
193161
// This catches the specific error for misconfigured trust chain (e.g., leaf in wrong place).
194162
throw new IOException("Trust chain misconfiguration: " + e.getMessage(), e);
195163
} catch (NoSuchFileException e) {
196164
throw new IOException(String.format("Trust chain file not found: %s", trustChainPath), e);
197165
} catch (CertificateException e) {
198-
// This catches parsing errors if certificates in the trust chain file are invalid.
199166
throw new IOException(
200167
String.format("Failed to parse certificate(s) from trust chain file: %s", trustChainPath),
201168
e);
@@ -209,6 +176,58 @@ public String getSubjectToken(ExternalAccountSupplierContext context) throws IOE
209176
return OAuth2Utils.JSON_FACTORY.toString(certChain);
210177
}
211178

179+
/**
180+
* Extends {@code certChainToPopulate} with encoded certificates from {@code trustChainCerts},
181+
* applying validation rules for the leaf certificate's presence and order within the trust chain.
182+
*
183+
* @param certChainToPopulate The list of encoded certificate strings to populate.
184+
* @param trustChainCerts The list of X509Certificates from the trust chain file (non-empty).
185+
* @param encodedLeafCert The Base64-encoded leaf certificate.
186+
* @throws CertificateEncodingException If an error occurs during certificate encoding.
187+
* @throws IllegalArgumentException If the leaf certificate is found in an invalid position in the
188+
* trust chain.
189+
*/
190+
private void populateCertChainFromTrustChain(
191+
List<String> certChainToPopulate,
192+
List<X509Certificate> trustChainCerts,
193+
String encodedLeafCert)
194+
throws CertificateEncodingException, IllegalArgumentException {
195+
196+
// Get the first certificate from the user-provided trust chain file.
197+
X509Certificate firstTrustCert = trustChainCerts.get(0);
198+
String encodedFirstTrustCert = encodeCert(firstTrustCert);
199+
200+
// If the first certificate in the user-provided trust chain file is *not* the leaf
201+
// certificate (which has already been added as the first element to `certChainToPopulate`),
202+
// then add this certificate. This handles cases where the user's trust chain file
203+
// starts with an intermediate certificate. If the first certificate in the trust chain file
204+
// *is* the leaf certificate, this means the user has explicitly included the leaf in their
205+
// trust chain file. In this case, we skip adding it again to prevent duplication, as the
206+
// leaf is already at the beginning of `certChainToPopulate`.
207+
if (!encodedFirstTrustCert.equals(encodedLeafCert)) {
208+
certChainToPopulate.add(encodedFirstTrustCert);
209+
}
210+
211+
// Iterate over the remaining certificates in the trust chain.
212+
for (int i = 1; i < trustChainCerts.size(); i++) {
213+
X509Certificate currentCert = trustChainCerts.get(i);
214+
String encodedCurrentCert = encodeCert(currentCert);
215+
216+
// Throw an error if the current certificate (from the user-provided trust chain file,
217+
// at an index beyond the first) is the same as the leaf certificate.
218+
// This enforces that if the leaf certificate is included in the trust chain file by the
219+
// user, it must be the very first certificate in that file. It should not appear
220+
// elsewhere in the chain.
221+
if (encodedCurrentCert.equals(encodedLeafCert)) {
222+
throw new IllegalArgumentException(
223+
"The leaf certificate should only appear at the beginning of the trust chain file, or be omitted entirely.");
224+
}
225+
226+
// Add the current certificate to the chain.
227+
certChainToPopulate.add(encodedCurrentCert);
228+
}
229+
}
230+
212231
/**
213232
* Reads a file containing PEM-encoded X509 certificates and returns a list of parsed
214233
* certificates. It splits the file content based on PEM headers and parses each certificate.
@@ -237,13 +256,14 @@ static List<X509Certificate> readTrustChain(String trustChainPath)
237256
trustChainData = Files.readAllBytes(Paths.get(trustChainPath));
238257

239258
// Split the file content into PEM certificate blocks.
240-
String content = new String(trustChainData);
259+
String content = new String(trustChainData, StandardCharsets.UTF_8);
241260

242261
Matcher matcher = PEM_CERT_PATTERN.matcher(content);
243262

244263
while (matcher.find()) {
245264
String pemCertBlock = matcher.group(0);
246-
try (InputStream certStream = new ByteArrayInputStream(pemCertBlock.getBytes())) {
265+
try (InputStream certStream =
266+
new ByteArrayInputStream(pemCertBlock.getBytes(StandardCharsets.UTF_8))) {
247267
// Parse the certificate data.
248268
Certificate cert = cf.generateCertificate(certStream);
249269

oauth2_http/javatests/com/google/auth/oauth2/CertificateIdentityPoolSubjectTokenSupplierTest.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ public void getSubjectToken_withoutTrustChain_success() throws Exception {
159159
}
160160

161161
@Test
162-
public void getSubjectToken_trustChainWithLeaf_success() throws Exception {
162+
public void getSubjectToken_trustChainWithLeafFirst_success() throws Exception {
163163
// Configure mock to return the path to the trust chain file with leaf.
164164
ClassLoader classLoader = getClass().getClassLoader();
165165
URL trustChainUrl = classLoader.getResource("trust_chain_with_leaf.pem");
@@ -227,6 +227,10 @@ public void getSubjectToken_trustChainWithoutLeaf_success() throws Exception {
227227
assertEquals(expectedSubjectToken, actualSubjectToken);
228228
}
229229

230+
// Tests that an IllegalArgumentException (wrapped in IOException) is thrown
231+
// when the trust chain file is provided and contains the leaf certificate,
232+
// but the leaf certificate is not the *first* certificate in that file.
233+
// For example, an intermediate certificate appears before the leaf certificate.
230234
@Test
231235
public void getSubjectToken_trustChainWrongOrder_throwsIllegalArgumentException() {
232236
ClassLoader classLoader = getClass().getClassLoader();
@@ -260,7 +264,9 @@ public void getSubjectToken_trustChainOnlyLeaf_success() throws Exception {
260264
// simulating a scenario where the trust chain file contains only the leaf.
261265
ClassLoader classLoader = getClass().getClassLoader();
262266
URL trustChainUrl = classLoader.getResource("x509_leaf_certificate.pem");
263-
assertNotNull("Leaf certificate file not found!", trustChainUrl);
267+
assertNotNull(
268+
"Test resource 'x509_leaf_certificate.pem' (used as trust chain) not found!",
269+
trustChainUrl);
264270
when(mockCertificateConfig.getTrustChainPath())
265271
.thenReturn(new File(trustChainUrl.getFile()).getAbsolutePath());
266272

0 commit comments

Comments
 (0)