3838import java .io .ByteArrayInputStream ;
3939import java .io .IOException ;
4040import java .io .InputStream ;
41+ import java .nio .charset .StandardCharsets ;
4142import java .nio .file .Files ;
4243import java .nio .file .NoSuchFileException ;
4344import 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
0 commit comments