@@ -1078,6 +1078,51 @@ void build_withCertificateSource_succeeds() throws Exception {
10781078 "Metrics header should indicate certificate source" );
10791079 }
10801080
1081+ @ Test
1082+ void build_withCertificateSourceAndCustomX509Provider_success ()
1083+ throws IOException , KeyStoreException , CertificateException , NoSuchAlgorithmException {
1084+ // Create an empty KeyStore and a spy on a custom X509Provider.
1085+ KeyStore keyStore = KeyStore .getInstance ("JKS" );
1086+ keyStore .load (null , null );
1087+ TestX509Provider x509Provider =
1088+ spy (new TestX509Provider (keyStore , "/path/to/certificate.json" ));
1089+
1090+ // Set up credential source for certificate type.
1091+ Map <String , Object > certificateMap = new HashMap <>();
1092+ certificateMap .put ("use_default_certificate_config" , true );
1093+ Map <String , Object > credentialSourceMap = new HashMap <>();
1094+ credentialSourceMap .put ("certificate" , certificateMap );
1095+ IdentityPoolCredentialSource credentialSource =
1096+ new IdentityPoolCredentialSource (credentialSourceMap );
1097+ MockExternalAccountCredentialsTransportFactory mockTransportFactory =
1098+ new MockExternalAccountCredentialsTransportFactory ();
1099+
1100+ // Build credentials with the custom provider.
1101+ IdentityPoolCredentials credentials =
1102+ IdentityPoolCredentials .newBuilder ()
1103+ .setX509Provider (x509Provider )
1104+ .setHttpTransportFactory (mockTransportFactory )
1105+ .setAudience ("test-audience" )
1106+ .setSubjectTokenType ("test-token-type" )
1107+ .setCredentialSource (credentialSource )
1108+ .build ();
1109+
1110+ // Verify successful creation and correct internal setup.
1111+ assertNotNull (credentials , "Credentials should be successfully created" );
1112+ assertTrue (
1113+ credentials .getIdentityPoolSubjectTokenSupplier ()
1114+ instanceof CertificateIdentityPoolSubjectTokenSupplier ,
1115+ "Subject token supplier should be for certificates" );
1116+ assertEquals (
1117+ IdentityPoolCredentials .CERTIFICATE_METRICS_HEADER_VALUE ,
1118+ credentials .getCredentialSourceType (),
1119+ "Metrics header should indicate certificate source" );
1120+
1121+ // Verify the custom provider methods were called during build.
1122+ verify (x509Provider ).getKeyStore ();
1123+ verify (x509Provider ).getCertificatePath ();
1124+ }
1125+
10811126 @ Test
10821127 void build_withDefaultCertificate_throwsOnTransportInitFailure () {
10831128 // Setup credential source to use default certificate config.
@@ -1104,6 +1149,81 @@ void build_withDefaultCertificate_throwsOnTransportInitFailure() {
11041149 exception .getMessage ());
11051150 }
11061151
1152+ @ Test
1153+ void build_withCustomProvider_throwsOnGetKeyStore ()
1154+ throws IOException , KeyStoreException , CertificateException , NoSuchAlgorithmException {
1155+ // Simulate a scenario where the X509Provider fails to load the KeyStore, typically due to an
1156+ // IOException when reading the certificate or private key files.
1157+ KeyStore keyStore = KeyStore .getInstance ("JKS" );
1158+ keyStore .load (null , null );
1159+ TestX509Provider x509Provider = new TestX509Provider (keyStore , "/path/to/certificate.json" );
1160+ x509Provider .setShouldThrowOnGetKeyStore (true ); // Configure to throw
1161+
1162+ Map <String , Object > certificateMap = new HashMap <>();
1163+ certificateMap .put ("certificate_config_location" , "/path/to/certificate.json" );
1164+
1165+ // Expect RuntimeException because the constructor wraps the IOException.
1166+ RuntimeException exception =
1167+ assertThrows (
1168+ RuntimeException .class ,
1169+ () -> createCredentialsWithCertificate (x509Provider , certificateMap ));
1170+
1171+ // Verify the cause is the expected IOException from the mock.
1172+ assertNotNull (exception .getCause ());
1173+ assertTrue (exception .getCause () instanceof IOException );
1174+ assertEquals ("Simulated IOException on get keystore" , exception .getCause ().getMessage ());
1175+
1176+ // Verify the wrapper exception message
1177+ assertEquals (
1178+ "Failed to initialize IdentityPoolCredentials from certificate source due to an I/O error." ,
1179+ exception .getMessage ());
1180+ }
1181+
1182+ @ Test
1183+ void build_withCustomProvider_throwsOnGetCertificatePath ()
1184+ throws IOException , KeyStoreException , CertificateException , NoSuchAlgorithmException {
1185+ // Simulate a scenario where the X509Provider cannot access or read the certificate
1186+ // configuration file needed to determine the certificate path, resulting in an IOException.
1187+ KeyStore keyStore = KeyStore .getInstance ("JKS" );
1188+ keyStore .load (null , null );
1189+ TestX509Provider x509Provider = new TestX509Provider (keyStore , "/path/to/certificate.json" );
1190+ x509Provider .setShouldThrowOnGetCertificatePath (true ); // Configure to throw
1191+
1192+ Map <String , Object > certificateMap = new HashMap <>();
1193+ certificateMap .put ("certificate_config_location" , "/path/to/certificate.json" );
1194+
1195+ // Expect RuntimeException because the constructor wraps the IOException.
1196+ RuntimeException exception =
1197+ assertThrows (
1198+ RuntimeException .class ,
1199+ () -> createCredentialsWithCertificate (x509Provider , certificateMap ));
1200+
1201+ // Verify the cause is the expected IOException from the mock.
1202+ assertNotNull (exception .getCause ());
1203+ assertTrue (exception .getCause () instanceof IOException );
1204+ assertEquals ("Simulated IOException on certificate path" , exception .getCause ().getMessage ());
1205+
1206+ // Verify the wrapper exception message
1207+ assertEquals (
1208+ "Failed to initialize IdentityPoolCredentials from certificate source due to an I/O error." ,
1209+ exception .getMessage ());
1210+ }
1211+
1212+ private void createCredentialsWithCertificate (
1213+ X509Provider x509Provider , Map <String , Object > certificateMap ) {
1214+ Map <String , Object > credentialSourceMap = new HashMap <>();
1215+ credentialSourceMap .put ("certificate" , certificateMap );
1216+ IdentityPoolCredentialSource credentialSource =
1217+ new IdentityPoolCredentialSource (credentialSourceMap );
1218+
1219+ IdentityPoolCredentials .newBuilder ()
1220+ .setX509Provider (x509Provider )
1221+ .setHttpTransportFactory (new MockExternalAccountCredentialsTransportFactory ())
1222+ .setAudience ("" )
1223+ .setSubjectTokenType ("" )
1224+ .setCredentialSource (credentialSource )
1225+ .build ();
1226+ }
11071227
11081228 static InputStream writeIdentityPoolCredentialsStream (
11091229 String tokenUrl ,
@@ -1186,5 +1306,40 @@ public HttpTransport create() {
11861306 }
11871307 }
11881308
1309+ static class TestX509Provider extends X509Provider {
1310+ private final KeyStore keyStore ;
1311+ private final String certPath ;
1312+ private boolean throwOnGetKeyStore ;
1313+ private boolean throwOnGetCertificatePath ;
1314+
1315+ public TestX509Provider (KeyStore keyStore , String certPath ) {
1316+ super ();
1317+ this .keyStore = keyStore ;
1318+ this .certPath = certPath ;
1319+ }
1320+
1321+ public void setShouldThrowOnGetKeyStore (boolean throwOnGetKeyStore ) {
1322+ this .throwOnGetKeyStore = throwOnGetKeyStore ;
1323+ }
1324+
1325+ public void setShouldThrowOnGetCertificatePath (boolean throwOnGetCertificatePath ) {
1326+ this .throwOnGetCertificatePath = throwOnGetCertificatePath ;
1327+ }
1328+
1329+ @ Override
1330+ public KeyStore getKeyStore () throws IOException {
1331+ if (throwOnGetKeyStore ) {
1332+ throw new IOException ("Simulated IOException on get keystore" );
1333+ }
1334+ return keyStore ;
1335+ }
11891336
1337+ @ Override
1338+ public String getCertificatePath () throws IOException {
1339+ if (throwOnGetCertificatePath ) {
1340+ throw new IOException ("Simulated IOException on certificate path" );
1341+ }
1342+ return certPath ;
1343+ }
1344+ }
11901345}
0 commit comments