@@ -62,6 +62,9 @@ public class CertificateIdentityPoolSubjectTokenSupplier
6262
6363 private final IdentityPoolCredentialSource credentialSource ;
6464
65+ private static final Pattern PEM_CERT_PATTERN =
66+ Pattern .compile ("-----BEGIN CERTIFICATE-----.*?-----END CERTIFICATE-----" , Pattern .DOTALL );
67+
6568 CertificateIdentityPoolSubjectTokenSupplier (IdentityPoolCredentialSource credentialSource ) {
6669 this .credentialSource = checkNotNull (credentialSource , "credentialSource cannot be null" );
6770 // This check ensures that the credential source was intended for certificate usage.
@@ -72,10 +75,22 @@ public class CertificateIdentityPoolSubjectTokenSupplier
7275 + " CertificateIdentityPoolSubjectTokenSupplier" );
7376 }
7477
75- private static X509Certificate loadLeafCertificate (String path )
76- throws IOException , CertificateException {
77- byte [] leafCertBytes = Files .readAllBytes (Paths .get (path ));
78- return parseCertificate (leafCertBytes );
78+ private static String loadAndEncodeLeafCertificate (String path ) throws IOException {
79+ try {
80+ byte [] leafCertBytes = Files .readAllBytes (Paths .get (path ));
81+ X509Certificate leafCert = parseCertificate (leafCertBytes );
82+ return encodeCert (leafCert );
83+ } catch (NoSuchFileException e ) {
84+ throw new IOException (String .format ("Leaf certificate file not found: %s" , path ), e );
85+ } catch (CertificateException e ) {
86+ // This catches parsing errors if the leaf certificate file is invalid.
87+ throw new IOException (
88+ String .format ("Failed to parse leaf certificate from file: %s" , path ), e );
89+ } catch (IOException e ) {
90+ // This catches any other general I/O errors during leaf certificate file reading (e.g.,
91+ // permissions).
92+ throw new IOException (String .format ("Failed to read leaf certificate file: %s" , path ), e );
93+ }
7994 }
8095
8196 @ VisibleForTesting
@@ -123,25 +138,33 @@ public String getSubjectToken(ExternalAccountSupplierContext context) throws IOE
123138 trustChainPath = credentialSource .getCertificateConfig ().getTrustChainPath ();
124139 }
125140
126- try {
127- // Load the leaf certificate.
128- X509Certificate leafCert = loadLeafCertificate (leafCertPath );
129- String encodedLeafCert = encodeCert (leafCert );
141+ // Load and encode the leaf certificate.
142+ String encodedLeafCert = loadAndEncodeLeafCertificate (leafCertPath );
130143
131- // Add the leaf certificate first.
132- java .util .List <String > certChain = new java .util .ArrayList <>();
133- certChain .add (encodedLeafCert );
144+ // Initialize the certificate chain for the subject token. The Security Token Service (STS)
145+ // requires that the leaf certificate (the one used for authenticating this workload) must be
146+ // the first certificate in this chain.
147+ java .util .List <String > certChain = new java .util .ArrayList <>();
148+ certChain .add (encodedLeafCert );
134149
150+ // Handle trust chain loading and processing.
151+ try {
135152 // Read the trust chain.
136153 List <X509Certificate > trustChainCerts = readTrustChain (trustChainPath );
137154
138155 // Process the trust chain certificates read from the file.
139156 if (!trustChainCerts .isEmpty ()) {
140- // Check the first certificate in the trust chain file.
157+ // Get the first certificate from the user-provided trust chain file.
141158 X509Certificate firstTrustCert = trustChainCerts .get (0 );
142159 String encodedFirstTrustCert = encodeCert (firstTrustCert );
143160
144- // Add the first certificate only if it is not the same as the leaf certificate.
161+ // If the first certificate in the user-provided trust chain file is *not* the leaf
162+ // certificate (which has already been added as the first element to `certChain`), then add
163+ // this certificate to `certChain`. This handles cases where the user's trust chain file
164+ // starts with an intermediate certificate. If the first certificate in the trust chain file
165+ // *is* the leaf certificate, this means the user has explicitly included the leaf in their
166+ // trust chain file. In this case, we skip adding it again to prevent duplication, as the
167+ // leaf is already at the beginning of `certChain`.
145168 if (!encodedFirstTrustCert .equals (encodedLeafCert )) {
146169 certChain .add (encodedFirstTrustCert );
147170 }
@@ -151,7 +174,11 @@ public String getSubjectToken(ExternalAccountSupplierContext context) throws IOE
151174 X509Certificate currentCert = trustChainCerts .get (i );
152175 String encodedCurrentCert = encodeCert (currentCert );
153176
154- // Throw an error if the current certificate is the same as the leaf certificate.
177+ // Throw an error if the current certificate (from the user-provided trust chain file,
178+ // at an index beyond the first) is the same as the leaf certificate.
179+ // This enforces that if the leaf certificate is included in the trust chain file by the
180+ // user, it must be the very first certificate in that file. It should not appear
181+ // elsewhere in the chain.
155182 if (encodedCurrentCert .equals (encodedLeafCert )) {
156183 throw new IllegalArgumentException (
157184 "The leaf certificate should only appear at the beginning of the trust chain file, or be omitted entirely." );
@@ -161,25 +188,24 @@ public String getSubjectToken(ExternalAccountSupplierContext context) throws IOE
161188 certChain .add (encodedCurrentCert );
162189 }
163190 }
164-
165- return OAuth2Utils .JSON_FACTORY .toString (certChain );
166- }
167- // The following catch blocks handle specific exceptions that can occur during
168- // certificate loading and parsing. These exceptions are wrapped in a new IOException,
169- // as declared by this method's signature.
170- catch (NoSuchFileException e ) {
171- // Handles the case where the leaf certificate file itself cannot be found.
172- throw new IOException (String .format ("Leaf certificate file not found: %s" , leafCertPath ), e );
191+ } catch (IllegalArgumentException e ) {
192+ // This catches the specific error for misconfigured trust chain (e.g., leaf in wrong place).
193+ throw new IOException ("Trust chain misconfiguration: " + e .getMessage (), e );
194+ } catch (NoSuchFileException e ) {
195+ throw new IOException (String .format ("Trust chain file not found: %s" , trustChainPath ), e );
173196 } catch (CertificateException e ) {
174- // Handles errors during the parsing of certificate data, which could stem from
175- // issues in either the leaf certificate or the trust chain. The message includes
176- // paths to both for comprehensive error reporting.
197+ // This catches parsing errors if certificates in the trust chain file are invalid.
177198 throw new IOException (
178- "Failed to read certificate file(s). Leaf path: "
179- + leafCertPath
180- + (trustChainPath != null ? "\n Trust chain path: " + trustChainPath : "" ),
199+ String .format ("Failed to parse certificate(s) from trust chain file: %s" , trustChainPath ),
181200 e );
201+ } catch (IOException e ) {
202+ // This catches any other general I/O errors during trust chain file reading (e.g.,
203+ // permissions).
204+ throw new IOException (
205+ String .format ("Failed to read trust chain file: %s" , trustChainPath ), e );
182206 }
207+
208+ return OAuth2Utils .JSON_FACTORY .toString (certChain );
183209 }
184210
185211 /**
@@ -207,19 +233,12 @@ static List<X509Certificate> readTrustChain(String trustChainPath)
207233
208234 // Read the trust chain file.
209235 byte [] trustChainData ;
210- try {
211- trustChainData = Files .readAllBytes (Paths .get (trustChainPath ));
212- } catch (NoSuchFileException e ) {
213- throw new IOException ("Trust chain file not found: " + trustChainPath , e );
214- } catch (IOException e ) {
215- throw new IOException ("Failed to read trust chain file: " + trustChainPath , e );
216- }
236+ trustChainData = Files .readAllBytes (Paths .get (trustChainPath ));
217237
218238 // Split the file content into PEM certificate blocks.
219239 String content = new String (trustChainData );
220- Pattern pemCertPattern =
221- Pattern .compile ("-----BEGIN CERTIFICATE-----.*?-----END CERTIFICATE-----" , Pattern .DOTALL );
222- Matcher matcher = pemCertPattern .matcher (content );
240+
241+ Matcher matcher = PEM_CERT_PATTERN .matcher (content );
223242
224243 while (matcher .find ()) {
225244 String pemCertBlock = matcher .group (0 );
0 commit comments