xds: use the new cert-provider instances if present (#8494)

diff --git a/xds/src/main/java/io/grpc/xds/ClientXdsClient.java b/xds/src/main/java/io/grpc/xds/ClientXdsClient.java
index 8384551..21cf78b 100644
--- a/xds/src/main/java/io/grpc/xds/ClientXdsClient.java
+++ b/xds/src/main/java/io/grpc/xds/ClientXdsClient.java
@@ -456,10 +456,6 @@
     if (commonTlsContext.hasTlsParams()) {
       throw new ResourceInvalidException("common-tls-context with tls_params is not supported");
     }
-    if (commonTlsContext.hasValidationContext()) {
-      throw new ResourceInvalidException(
-          "common-tls-context with validation_context is not supported");
-    }
     if (commonTlsContext.hasValidationContextSdsSecretConfig()) {
       throw new ResourceInvalidException(
           "common-tls-context with validation_context_sds_secret_config is not supported");
@@ -473,54 +469,50 @@
           "common-tls-context with validation_context_certificate_provider_instance is not"
               + " supported");
     }
-    String certInstanceName = null;
-    if (!commonTlsContext.hasTlsCertificateCertificateProviderInstance()) {
+    String certInstanceName = getIdentityCertInstanceName(commonTlsContext);
+    if (certInstanceName == null) {
       if (server) {
         throw new ResourceInvalidException(
-            "tls_certificate_certificate_provider_instance is required in downstream-tls-context");
+            "tls_certificate_provider_instance is required in downstream-tls-context");
       }
       if (commonTlsContext.getTlsCertificatesCount() > 0) {
         throw new ResourceInvalidException(
-            "common-tls-context with tls_certificates is not supported");
+            "tls_certificate_provider_instance is unset");
       }
       if (commonTlsContext.getTlsCertificateSdsSecretConfigsCount() > 0) {
         throw new ResourceInvalidException(
-            "common-tls-context with tls_certificate_sds_secret_configs is not supported");
+            "tls_certificate_provider_instance is unset");
       }
       if (commonTlsContext.hasTlsCertificateCertificateProvider()) {
         throw new ResourceInvalidException(
-            "common-tls-context with tls_certificate_certificate_provider is not supported");
+            "tls_certificate_provider_instance is unset");
       }
-    } else {
-      certInstanceName = commonTlsContext.getTlsCertificateCertificateProviderInstance()
-          .getInstanceName();
+    } else if (certProviderInstances == null || !certProviderInstances.contains(certInstanceName)) {
+      throw new ResourceInvalidException(
+          "CertificateProvider instance name '" + certInstanceName
+              + "' not defined in the bootstrap file.");
     }
-    if (certInstanceName != null) {
-      if (certProviderInstances == null || !certProviderInstances.contains(certInstanceName)) {
-        throw new ResourceInvalidException(
-            "CertificateProvider instance name '" + certInstanceName
-                + "' not defined in the bootstrap file.");
-      }
-    }
-    String rootCaInstanceName = null;
-    if (!commonTlsContext.hasCombinedValidationContext()) {
+    String rootCaInstanceName = getRootCertInstanceName(commonTlsContext);
+    if (rootCaInstanceName == null) {
       if (!server) {
         throw new ResourceInvalidException(
-            "combined_validation_context is required in upstream-tls-context");
+            "ca_certificate_provider_instance is required in upstream-tls-context");
       }
     } else {
-      CommonTlsContext.CombinedCertificateValidationContext combinedCertificateValidationContext
-          = commonTlsContext.getCombinedValidationContext();
-      if (!combinedCertificateValidationContext.hasValidationContextCertificateProviderInstance()) {
+      if (certProviderInstances == null || !certProviderInstances.contains(rootCaInstanceName)) {
         throw new ResourceInvalidException(
-            "validation_context_certificate_provider_instance is required in"
-                + " combined_validation_context");
+                "ca_certificate_provider_instance name '" + rootCaInstanceName
+                        + "' not defined in the bootstrap file.");
       }
-      rootCaInstanceName = combinedCertificateValidationContext
-          .getValidationContextCertificateProviderInstance().getInstanceName();
-      if (combinedCertificateValidationContext.hasDefaultValidationContext()) {
-        CertificateValidationContext certificateValidationContext
-            = combinedCertificateValidationContext.getDefaultValidationContext();
+      CertificateValidationContext certificateValidationContext = null;
+      if (commonTlsContext.hasValidationContext()) {
+        certificateValidationContext = commonTlsContext.getValidationContext();
+      } else if (commonTlsContext.hasCombinedValidationContext() && commonTlsContext
+          .getCombinedValidationContext().hasDefaultValidationContext()) {
+        certificateValidationContext = commonTlsContext.getCombinedValidationContext()
+            .getDefaultValidationContext();
+      }
+      if (certificateValidationContext != null) {
         if (certificateValidationContext.getMatchSubjectAltNamesCount() > 0 && server) {
           throw new ResourceInvalidException(
               "match_subject_alt_names only allowed in upstream_tls_context");
@@ -547,13 +539,38 @@
         }
       }
     }
-    if (rootCaInstanceName != null) {
-      if (certProviderInstances == null || !certProviderInstances.contains(rootCaInstanceName)) {
-        throw new ResourceInvalidException(
-            "ValidationContextProvider instance name '" + rootCaInstanceName
-                + "' not defined in the bootstrap file.");
+  }
+
+  private static String getIdentityCertInstanceName(CommonTlsContext commonTlsContext) {
+    if (commonTlsContext.hasTlsCertificateProviderInstance()) {
+      return commonTlsContext.getTlsCertificateProviderInstance().getInstanceName();
+    } else if (commonTlsContext.hasTlsCertificateCertificateProviderInstance()) {
+      return commonTlsContext.getTlsCertificateCertificateProviderInstance().getInstanceName();
+    }
+    return null;
+  }
+
+  private static String getRootCertInstanceName(CommonTlsContext commonTlsContext) {
+    if (commonTlsContext.hasValidationContext()) {
+      if (commonTlsContext.getValidationContext().hasCaCertificateProviderInstance()) {
+        return commonTlsContext.getValidationContext().getCaCertificateProviderInstance()
+            .getInstanceName();
+      }
+    } else if (commonTlsContext.hasCombinedValidationContext()) {
+      CommonTlsContext.CombinedCertificateValidationContext combinedCertificateValidationContext
+          = commonTlsContext.getCombinedValidationContext();
+      if (combinedCertificateValidationContext.hasDefaultValidationContext()
+          && combinedCertificateValidationContext.getDefaultValidationContext()
+          .hasCaCertificateProviderInstance()) {
+        return combinedCertificateValidationContext.getDefaultValidationContext()
+            .getCaCertificateProviderInstance().getInstanceName();
+      } else if (combinedCertificateValidationContext
+          .hasValidationContextCertificateProviderInstance()) {
+        return combinedCertificateValidationContext
+            .getValidationContextCertificateProviderInstance().getInstanceName();
       }
     }
+    return null;
   }
 
   private static void checkForUniqueness(Set<FilterChainMatch> uniqueSet,
diff --git a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderClientSslContextProvider.java b/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderClientSslContextProvider.java
index 2ee21e7..ce9ef3d 100644
--- a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderClientSslContextProvider.java
+++ b/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderClientSslContextProvider.java
@@ -22,7 +22,6 @@
 import io.envoyproxy.envoy.config.core.v3.Node;
 import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext;
 import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext;
-import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext.CombinedCertificateValidationContext;
 import io.grpc.Internal;
 import io.grpc.netty.GrpcSslContexts;
 import io.grpc.xds.Bootstrapper.CertificateProviderInfo;
@@ -94,27 +93,12 @@
         @Nullable Map<String, CertificateProviderInfo> certProviders) {
       checkNotNull(upstreamTlsContext, "upstreamTlsContext");
       CommonTlsContext commonTlsContext = upstreamTlsContext.getCommonTlsContext();
-      CommonTlsContext.CertificateProviderInstance rootCertInstance = null;
-      CertificateValidationContext staticCertValidationContext = null;
-      if (commonTlsContext.hasCombinedValidationContext()) {
-        CombinedCertificateValidationContext combinedValidationContext =
-            commonTlsContext.getCombinedValidationContext();
-        if (combinedValidationContext.hasValidationContextCertificateProviderInstance()) {
-          rootCertInstance =
-              combinedValidationContext.getValidationContextCertificateProviderInstance();
-        }
-        if (combinedValidationContext.hasDefaultValidationContext()) {
-          staticCertValidationContext = combinedValidationContext.getDefaultValidationContext();
-        }
-      } else if (commonTlsContext.hasValidationContextCertificateProviderInstance()) {
-        rootCertInstance = commonTlsContext.getValidationContextCertificateProviderInstance();
-      } else if (commonTlsContext.hasValidationContext()) {
-        staticCertValidationContext = commonTlsContext.getValidationContext();
-      }
-      CommonTlsContext.CertificateProviderInstance certInstance = null;
-      if (commonTlsContext.hasTlsCertificateCertificateProviderInstance()) {
-        certInstance = commonTlsContext.getTlsCertificateCertificateProviderInstance();
-      }
+      CertificateValidationContext staticCertValidationContext = getStaticValidationContext(
+          commonTlsContext);
+      CommonTlsContext.CertificateProviderInstance rootCertInstance = getRootCertProviderInstance(
+          commonTlsContext);
+      CommonTlsContext.CertificateProviderInstance certInstance = getCertProviderInstance(
+          commonTlsContext);
       return new CertProviderClientSslContextProvider(
           node,
           certProviders,
diff --git a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderServerSslContextProvider.java b/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderServerSslContextProvider.java
index 1f33e1d..a7f0849 100644
--- a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderServerSslContextProvider.java
+++ b/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderServerSslContextProvider.java
@@ -22,7 +22,6 @@
 import io.envoyproxy.envoy.config.core.v3.Node;
 import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext;
 import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext;
-import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext.CombinedCertificateValidationContext;
 import io.grpc.Internal;
 import io.grpc.netty.GrpcSslContexts;
 import io.grpc.xds.Bootstrapper.CertificateProviderInfo;
@@ -97,27 +96,12 @@
         @Nullable Map<String, CertificateProviderInfo> certProviders) {
       checkNotNull(downstreamTlsContext, "downstreamTlsContext");
       CommonTlsContext commonTlsContext = downstreamTlsContext.getCommonTlsContext();
-      CommonTlsContext.CertificateProviderInstance rootCertInstance = null;
-      CertificateValidationContext staticCertValidationContext = null;
-      if (commonTlsContext.hasCombinedValidationContext()) {
-        CombinedCertificateValidationContext combinedValidationContext =
-            commonTlsContext.getCombinedValidationContext();
-        if (combinedValidationContext.hasValidationContextCertificateProviderInstance()) {
-          rootCertInstance =
-              combinedValidationContext.getValidationContextCertificateProviderInstance();
-        }
-        if (combinedValidationContext.hasDefaultValidationContext()) {
-          staticCertValidationContext = combinedValidationContext.getDefaultValidationContext();
-        }
-      } else if (commonTlsContext.hasValidationContextCertificateProviderInstance()) {
-        rootCertInstance = commonTlsContext.getValidationContextCertificateProviderInstance();
-      } else if (commonTlsContext.hasValidationContext()) {
-        staticCertValidationContext = commonTlsContext.getValidationContext();
-      }
-      CommonTlsContext.CertificateProviderInstance certInstance = null;
-      if (commonTlsContext.hasTlsCertificateCertificateProviderInstance()) {
-        certInstance = commonTlsContext.getTlsCertificateCertificateProviderInstance();
-      }
+      CertificateValidationContext staticCertValidationContext = getStaticValidationContext(
+          commonTlsContext);
+      CommonTlsContext.CertificateProviderInstance rootCertInstance = getRootCertProviderInstance(
+          commonTlsContext);
+      CommonTlsContext.CertificateProviderInstance certInstance = getCertProviderInstance(
+          commonTlsContext);
       return new CertProviderServerSslContextProvider(
           node,
           certProviders,
diff --git a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderSslContextProvider.java b/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderSslContextProvider.java
index 1af9e16..1ec5876 100644
--- a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderSslContextProvider.java
+++ b/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderSslContextProvider.java
@@ -18,9 +18,11 @@
 
 import io.envoyproxy.envoy.config.core.v3.Node;
 import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext;
+import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext;
 import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext.CertificateProviderInstance;
 import io.grpc.xds.Bootstrapper.CertificateProviderInfo;
 import io.grpc.xds.EnvoyServerProtoData.BaseTlsContext;
+import io.grpc.xds.internal.sds.CommonTlsContextUtil;
 import io.grpc.xds.internal.sds.DynamicSslContextProvider;
 import java.security.PrivateKey;
 import java.security.cert.X509Certificate;
@@ -88,6 +90,52 @@
     return certProviders != null ? certProviders.get(pluginInstanceName) : null;
   }
 
+  @Nullable
+  protected static CertificateProviderInstance getCertProviderInstance(
+      CommonTlsContext commonTlsContext) {
+    if (commonTlsContext.hasTlsCertificateProviderInstance()) {
+      return CommonTlsContextUtil.convert(commonTlsContext.getTlsCertificateProviderInstance());
+    } else if (commonTlsContext.hasTlsCertificateCertificateProviderInstance()) {
+      return commonTlsContext.getTlsCertificateCertificateProviderInstance();
+    }
+    return null;
+  }
+
+  @Nullable
+  protected static CertificateValidationContext getStaticValidationContext(
+      CommonTlsContext commonTlsContext) {
+    if (commonTlsContext.hasValidationContext()) {
+      return commonTlsContext.getValidationContext();
+    } else if (commonTlsContext.hasCombinedValidationContext()) {
+      CommonTlsContext.CombinedCertificateValidationContext combinedValidationContext =
+          commonTlsContext.getCombinedValidationContext();
+      if (combinedValidationContext.hasDefaultValidationContext()) {
+        return combinedValidationContext.getDefaultValidationContext();
+      }
+    }
+    return null;
+  }
+
+  @Nullable
+  protected static CommonTlsContext.CertificateProviderInstance getRootCertProviderInstance(
+      CommonTlsContext commonTlsContext) {
+    CertificateValidationContext certValidationContext = getStaticValidationContext(
+        commonTlsContext);
+    if (certValidationContext != null && certValidationContext.hasCaCertificateProviderInstance()) {
+      return CommonTlsContextUtil.convert(certValidationContext.getCaCertificateProviderInstance());
+    }
+    if (commonTlsContext.hasCombinedValidationContext()) {
+      CommonTlsContext.CombinedCertificateValidationContext combinedValidationContext =
+          commonTlsContext.getCombinedValidationContext();
+      if (combinedValidationContext.hasValidationContextCertificateProviderInstance()) {
+        return combinedValidationContext.getValidationContextCertificateProviderInstance();
+      }
+    } else if (commonTlsContext.hasValidationContextCertificateProviderInstance()) {
+      return commonTlsContext.getValidationContextCertificateProviderInstance();
+    }
+    return null;
+  }
+
   @Override
   public final void updateCertificate(PrivateKey key, List<X509Certificate> certChain) {
     savedKey = key;
diff --git a/xds/src/main/java/io/grpc/xds/internal/sds/CommonTlsContextUtil.java b/xds/src/main/java/io/grpc/xds/internal/sds/CommonTlsContextUtil.java
index 234989a..0c28c79 100644
--- a/xds/src/main/java/io/grpc/xds/internal/sds/CommonTlsContextUtil.java
+++ b/xds/src/main/java/io/grpc/xds/internal/sds/CommonTlsContextUtil.java
@@ -16,11 +16,12 @@
 
 package io.grpc.xds.internal.sds;
 
+import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateProviderPluginInstance;
 import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext;
 import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext.CombinedCertificateValidationContext;
 
 /** Class for utility functions for {@link CommonTlsContext}. */
-final class CommonTlsContextUtil {
+public final class CommonTlsContextUtil {
 
   private CommonTlsContextUtil() {}
 
@@ -38,4 +39,15 @@
     }
     return commonTlsContext.hasValidationContextCertificateProviderInstance();
   }
+
+  /**
+   * Converts {@link CertificateProviderPluginInstance} to
+   * {@link CommonTlsContext.CertificateProviderInstance}.
+   */
+  public static CommonTlsContext.CertificateProviderInstance convert(
+      CertificateProviderPluginInstance pluginInstance) {
+    return CommonTlsContext.CertificateProviderInstance.newBuilder()
+        .setInstanceName(pluginInstance.getInstanceName())
+        .setCertificateName(pluginInstance.getCertificateName()).build();
+  }
 }
diff --git a/xds/src/test/java/io/grpc/xds/ClientXdsClientDataTest.java b/xds/src/test/java/io/grpc/xds/ClientXdsClientDataTest.java
index fb5c349..80cd2a8 100644
--- a/xds/src/test/java/io/grpc/xds/ClientXdsClientDataTest.java
+++ b/xds/src/test/java/io/grpc/xds/ClientXdsClientDataTest.java
@@ -77,6 +77,7 @@
 import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager;
 import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpFilter;
 import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.Rds;
+import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateProviderPluginInstance;
 import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext;
 import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext;
 import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext.CertificateProviderInstance;
@@ -1551,7 +1552,7 @@
             .setValidationContext(CertificateValidationContext.getDefaultInstance())
             .build();
     thrown.expect(ResourceInvalidException.class);
-    thrown.expectMessage("common-tls-context with validation_context is not supported");
+    thrown.expectMessage("ca_certificate_provider_instance is required in upstream-tls-context");
     ClientXdsClient.validateCommonTlsContext(commonTlsContext, null, false);
   }
 
@@ -1603,14 +1604,26 @@
         .build();
     thrown.expect(ResourceInvalidException.class);
     thrown.expectMessage(
-        "tls_certificate_certificate_provider_instance is required in downstream-tls-context");
+        "tls_certificate_provider_instance is required in downstream-tls-context");
     ClientXdsClient.validateCommonTlsContext(commonTlsContext, null, true);
   }
 
   @Test
   @SuppressWarnings("deprecation")
+  public void validateCommonTlsContext_tlsNewCertificateProviderInstance()
+      throws ResourceInvalidException {
+    CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
+        .setTlsCertificateProviderInstance(
+            CertificateProviderPluginInstance.newBuilder().setInstanceName("name1").build())
+        .build();
+    ClientXdsClient
+        .validateCommonTlsContext(commonTlsContext, ImmutableSet.of("name1", "name2"), true);
+  }
+
+  @Test
+  @SuppressWarnings("deprecation")
   public void validateCommonTlsContext_tlsCertificateProviderInstance()
-          throws ResourceInvalidException {
+      throws ResourceInvalidException {
     CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
         .setTlsCertificateCertificateProviderInstance(
             CertificateProviderInstance.newBuilder().setInstanceName("name1").build())
@@ -1662,7 +1675,7 @@
         .build();
     thrown.expect(ResourceInvalidException.class);
     thrown.expectMessage(
-        "ValidationContextProvider instance name 'bad-name' not defined in the bootstrap file.");
+        "ca_certificate_provider_instance name 'bad-name' not defined in the bootstrap file.");
     ClientXdsClient
         .validateCommonTlsContext(commonTlsContext, ImmutableSet.of("name1", "name2"), false);
   }
@@ -1674,7 +1687,7 @@
             .addTlsCertificates(TlsCertificate.getDefaultInstance())
             .build();
     thrown.expect(ResourceInvalidException.class);
-    thrown.expectMessage("common-tls-context with tls_certificates is not supported");
+    thrown.expectMessage("tls_certificate_provider_instance is unset");
     ClientXdsClient.validateCommonTlsContext(commonTlsContext, null, false);
   }
 
@@ -1686,7 +1699,7 @@
         .build();
     thrown.expect(ResourceInvalidException.class);
     thrown.expectMessage(
-        "common-tls-context with tls_certificate_sds_secret_configs is not supported");
+        "tls_certificate_provider_instance is unset");
     ClientXdsClient.validateCommonTlsContext(commonTlsContext, null, false);
   }
 
@@ -1700,7 +1713,7 @@
         .build();
     thrown.expect(ResourceInvalidException.class);
     thrown.expectMessage(
-        "common-tls-context with tls_certificate_certificate_provider is not supported");
+        "tls_certificate_provider_instance is unset");
     ClientXdsClient.validateCommonTlsContext(commonTlsContext, null, false);
   }
 
@@ -1710,7 +1723,7 @@
     CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
         .build();
     thrown.expect(ResourceInvalidException.class);
-    thrown.expectMessage("combined_validation_context is required in upstream-tls-context");
+    thrown.expectMessage("ca_certificate_provider_instance is required in upstream-tls-context");
     ClientXdsClient.validateCommonTlsContext(commonTlsContext, null, false);
   }
 
@@ -1723,8 +1736,7 @@
         .build();
     thrown.expect(ResourceInvalidException.class);
     thrown.expectMessage(
-        "validation_context_certificate_provider_instance is required in "
-            + "combined_validation_context");
+        "ca_certificate_provider_instance is required in upstream-tls-context");
     ClientXdsClient.validateCommonTlsContext(commonTlsContext, null, false);
   }
 
diff --git a/xds/src/test/java/io/grpc/xds/ClientXdsClientTestBase.java b/xds/src/test/java/io/grpc/xds/ClientXdsClientTestBase.java
index 9e4d92f..55bd6ba 100644
--- a/xds/src/test/java/io/grpc/xds/ClientXdsClientTestBase.java
+++ b/xds/src/test/java/io/grpc/xds/ClientXdsClientTestBase.java
@@ -39,6 +39,7 @@
 import com.google.protobuf.InvalidProtocolBufferException;
 import com.google.protobuf.Message;
 import io.envoyproxy.envoy.config.route.v3.FilterConfig;
+import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateProviderPluginInstance;
 import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext;
 import io.grpc.BindableService;
 import io.grpc.Context;
@@ -1354,6 +1355,42 @@
   }
 
   /**
+   * CDS response containing new UpstreamTlsContext for a cluster.
+   */
+  @Test
+  @SuppressWarnings("deprecation")
+  public void cdsResponseWithNewUpstreamTlsContext() {
+    Assume.assumeTrue(useProtocolV3());
+    DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher);
+
+    // Management server sends back CDS response with UpstreamTlsContext.
+    Any clusterEds =
+        Any.pack(mf.buildEdsCluster(CDS_RESOURCE, "eds-cluster-foo.googleapis.com", "round_robin",
+            null, true,
+            mf.buildNewUpstreamTlsContext("cert-instance-name", "cert1"),
+            "envoy.transport_sockets.tls", null));
+    List<Any> clusters = ImmutableList.of(
+        Any.pack(mf.buildLogicalDnsCluster("cluster-bar.googleapis.com",
+            "dns-service-bar.googleapis.com", 443, "round_robin", null, false, null, null)),
+        clusterEds,
+        Any.pack(mf.buildEdsCluster("cluster-baz.googleapis.com", null, "round_robin", null, false,
+            null, "envoy.transport_sockets.tls", null)));
+    call.sendResponse(CDS, clusters, VERSION_1, "0000");
+
+    // Client sent an ACK CDS request.
+    call.verifyRequest(CDS, CDS_RESOURCE, VERSION_1, "0000", NODE);
+    verify(cdsResourceWatcher, times(1)).onChanged(cdsUpdateCaptor.capture());
+    CdsUpdate cdsUpdate = cdsUpdateCaptor.getValue();
+    CertificateProviderPluginInstance certificateProviderInstance =
+        cdsUpdate.upstreamTlsContext().getCommonTlsContext().getValidationContext()
+            .getCaCertificateProviderInstance();
+    assertThat(certificateProviderInstance.getInstanceName()).isEqualTo("cert-instance-name");
+    assertThat(certificateProviderInstance.getCertificateName()).isEqualTo("cert1");
+    verifyResourceMetadataAcked(CDS, CDS_RESOURCE, clusterEds, VERSION_1, TIME_INCREMENT);
+    verifySubscribedResourcesMetadataSizes(0, 1, 0, 0);
+  }
+
+  /**
    * CDS response containing bad UpstreamTlsContext for a cluster.
    */
   @Test
@@ -1373,7 +1410,7 @@
         "CDS response Cluster 'cluster.googleapis.com' validation error: "
             + "Cluster cluster.googleapis.com: malformed UpstreamTlsContext: "
             + "io.grpc.xds.ClientXdsClient$ResourceInvalidException: "
-            + "combined_validation_context is required in upstream-tls-context"));
+            + "ca_certificate_provider_instance is required in upstream-tls-context"));
     verifyNoInteractions(cdsResourceWatcher);
   }
 
@@ -2400,6 +2437,8 @@
 
     protected abstract Message buildUpstreamTlsContext(String instanceName, String certName);
 
+    protected abstract Message buildNewUpstreamTlsContext(String instanceName, String certName);
+
     protected abstract Message buildCircuitBreakers(int highPriorityMaxRequests,
         int defaultPriorityMaxRequests);
 
diff --git a/xds/src/test/java/io/grpc/xds/ClientXdsClientV2Test.java b/xds/src/test/java/io/grpc/xds/ClientXdsClientV2Test.java
index 409613a..39f5d1a 100644
--- a/xds/src/test/java/io/grpc/xds/ClientXdsClientV2Test.java
+++ b/xds/src/test/java/io/grpc/xds/ClientXdsClientV2Test.java
@@ -516,6 +516,12 @@
     }
 
     @Override
+    protected Message buildNewUpstreamTlsContext(String instanceName, String certName) {
+      return buildUpstreamTlsContext(instanceName, certName);
+    }
+
+
+    @Override
     protected Message buildCircuitBreakers(int highPriorityMaxRequests,
         int defaultPriorityMaxRequests) {
       return CircuitBreakers.newBuilder()
diff --git a/xds/src/test/java/io/grpc/xds/ClientXdsClientV3Test.java b/xds/src/test/java/io/grpc/xds/ClientXdsClientV3Test.java
index eddba10..dfd407e 100644
--- a/xds/src/test/java/io/grpc/xds/ClientXdsClientV3Test.java
+++ b/xds/src/test/java/io/grpc/xds/ClientXdsClientV3Test.java
@@ -77,6 +77,8 @@
 import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager;
 import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpFilter;
 import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.Rds;
+import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateProviderPluginInstance;
+import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext;
 import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext;
 import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext;
 import io.envoyproxy.envoy.service.discovery.v3.AggregatedDiscoveryServiceGrpc.AggregatedDiscoveryServiceImplBase;
@@ -556,6 +558,20 @@
     }
 
     @Override
+    protected Message buildNewUpstreamTlsContext(String instanceName, String certName) {
+      CommonTlsContext.Builder commonTlsContextBuilder = CommonTlsContext.newBuilder();
+      if (instanceName != null && certName != null) {
+        commonTlsContextBuilder.setValidationContext(CertificateValidationContext.newBuilder()
+            .setCaCertificateProviderInstance(
+                CertificateProviderPluginInstance.newBuilder().setInstanceName(instanceName)
+                    .setCertificateName(certName).build()));
+      }
+      return UpstreamTlsContext.newBuilder()
+          .setCommonTlsContext(commonTlsContextBuilder)
+          .build();
+    }
+
+    @Override
     protected Message buildCircuitBreakers(int highPriorityMaxRequests,
         int defaultPriorityMaxRequests) {
       return CircuitBreakers.newBuilder()
diff --git a/xds/src/test/java/io/grpc/xds/internal/certprovider/CertProviderClientSslContextProviderTest.java b/xds/src/test/java/io/grpc/xds/internal/certprovider/CertProviderClientSslContextProviderTest.java
index 00b2901..1eed548 100644
--- a/xds/src/test/java/io/grpc/xds/internal/certprovider/CertProviderClientSslContextProviderTest.java
+++ b/xds/src/test/java/io/grpc/xds/internal/certprovider/CertProviderClientSslContextProviderTest.java
@@ -87,6 +87,27 @@
         bootstrapInfo.getCertProviders());
   }
 
+  /** Helper method to build CertProviderClientSslContextProvider. */
+  private CertProviderClientSslContextProvider getNewSslContextProvider(
+          String certInstanceName,
+          String rootInstanceName,
+          Bootstrapper.BootstrapInfo bootstrapInfo,
+          Iterable<String> alpnProtocols,
+          CertificateValidationContext staticCertValidationContext) {
+    EnvoyServerProtoData.UpstreamTlsContext upstreamTlsContext =
+            CommonTlsContextTestsUtil.buildNewUpstreamTlsContextForCertProviderInstance(
+                    certInstanceName,
+                    "cert-default",
+                    rootInstanceName,
+                    "root-default",
+                    alpnProtocols,
+                    staticCertValidationContext);
+    return certProviderClientSslContextProviderFactory.getProvider(
+            upstreamTlsContext,
+            bootstrapInfo.getNode().toEnvoyProtoNode(),
+            bootstrapInfo.getCertProviders());
+  }
+
   @Test
   public void testProviderForClient_mtls() throws Exception {
     final CertificateProvider.DistributorWatcher[] watcherCaptor =
@@ -151,6 +172,69 @@
   }
 
   @Test
+  public void testProviderForClient_mtls_newXds() throws Exception {
+    final CertificateProvider.DistributorWatcher[] watcherCaptor =
+            new CertificateProvider.DistributorWatcher[1];
+    TestCertificateProvider.createAndRegisterProviderProvider(
+            certificateProviderRegistry, watcherCaptor, "testca", 0);
+    CertProviderClientSslContextProvider provider =
+            getNewSslContextProvider(
+                    "gcp_id",
+                    "gcp_id",
+                    CommonBootstrapperTestUtils.getTestBootstrapInfo(),
+                    /* alpnProtocols= */ null,
+                    /* staticCertValidationContext= */ null);
+
+    assertThat(provider.savedKey).isNull();
+    assertThat(provider.savedCertChain).isNull();
+    assertThat(provider.savedTrustedRoots).isNull();
+    assertThat(provider.getSslContext()).isNull();
+
+    // now generate cert update
+    watcherCaptor[0].updateCertificate(
+            CommonCertProviderTestUtils.getPrivateKey(CLIENT_KEY_FILE),
+            ImmutableList.of(getCertFromResourceName(CLIENT_PEM_FILE)));
+    assertThat(provider.savedKey).isNotNull();
+    assertThat(provider.savedCertChain).isNotNull();
+    assertThat(provider.getSslContext()).isNull();
+
+    // now generate root cert update
+    watcherCaptor[0].updateTrustedRoots(ImmutableList.of(getCertFromResourceName(CA_PEM_FILE)));
+    assertThat(provider.getSslContext()).isNotNull();
+    assertThat(provider.savedKey).isNull();
+    assertThat(provider.savedCertChain).isNull();
+    assertThat(provider.savedTrustedRoots).isNull();
+
+    TestCallback testCallback =
+            CommonTlsContextTestsUtil.getValueThruCallback(provider);
+
+    doChecksOnSslContext(false, testCallback.updatedSslContext, /* expectedApnProtos= */ null);
+    TestCallback testCallback1 =
+            CommonTlsContextTestsUtil.getValueThruCallback(provider);
+    assertThat(testCallback1.updatedSslContext).isSameInstanceAs(testCallback.updatedSslContext);
+
+    // just do root cert update: sslContext should still be the same
+    watcherCaptor[0].updateTrustedRoots(
+            ImmutableList.of(getCertFromResourceName(SERVER_0_PEM_FILE)));
+    assertThat(provider.savedKey).isNull();
+    assertThat(provider.savedCertChain).isNull();
+    assertThat(provider.savedTrustedRoots).isNotNull();
+    testCallback1 = CommonTlsContextTestsUtil.getValueThruCallback(provider);
+    assertThat(testCallback1.updatedSslContext).isSameInstanceAs(testCallback.updatedSslContext);
+
+    // now update id cert: sslContext should be updated i.e.different from the previous one
+    watcherCaptor[0].updateCertificate(
+            CommonCertProviderTestUtils.getPrivateKey(SERVER_1_KEY_FILE),
+            ImmutableList.of(getCertFromResourceName(SERVER_1_PEM_FILE)));
+    assertThat(provider.savedKey).isNull();
+    assertThat(provider.savedCertChain).isNull();
+    assertThat(provider.savedTrustedRoots).isNull();
+    assertThat(provider.getSslContext()).isNotNull();
+    testCallback1 = CommonTlsContextTestsUtil.getValueThruCallback(provider);
+    assertThat(testCallback1.updatedSslContext).isNotSameInstanceAs(testCallback.updatedSslContext);
+  }
+
+  @Test
   public void testProviderForClient_queueExecutor() throws Exception {
     final CertificateProvider.DistributorWatcher[] watcherCaptor =
         new CertificateProvider.DistributorWatcher[1];
diff --git a/xds/src/test/java/io/grpc/xds/internal/certprovider/CertProviderServerSslContextProviderTest.java b/xds/src/test/java/io/grpc/xds/internal/certprovider/CertProviderServerSslContextProviderTest.java
index ef801cc..783ce2b 100644
--- a/xds/src/test/java/io/grpc/xds/internal/certprovider/CertProviderServerSslContextProviderTest.java
+++ b/xds/src/test/java/io/grpc/xds/internal/certprovider/CertProviderServerSslContextProviderTest.java
@@ -31,12 +31,14 @@
 import com.google.common.util.concurrent.MoreExecutors;
 import io.envoyproxy.envoy.config.core.v3.DataSource;
 import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext;
+import io.envoyproxy.envoy.type.matcher.v3.StringMatcher;
 import io.grpc.xds.Bootstrapper;
 import io.grpc.xds.CommonBootstrapperTestUtils;
 import io.grpc.xds.EnvoyServerProtoData;
 import io.grpc.xds.internal.certprovider.CertProviderClientSslContextProviderTest.QueuedExecutor;
 import io.grpc.xds.internal.sds.CommonTlsContextTestsUtil;
 import io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.TestCallback;
+import java.util.Arrays;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -81,6 +83,30 @@
         bootstrapInfo.getCertProviders());
   }
 
+  /** Helper method to build CertProviderServerSslContextProvider. */
+  private CertProviderServerSslContextProvider getNewSslContextProvider(
+          String certInstanceName,
+          String rootInstanceName,
+          Bootstrapper.BootstrapInfo bootstrapInfo,
+          Iterable<String> alpnProtocols,
+          CertificateValidationContext staticCertValidationContext,
+          boolean requireClientCert) {
+    EnvoyServerProtoData.DownstreamTlsContext downstreamTlsContext =
+            CommonTlsContextTestsUtil.buildNewDownstreamTlsContextForCertProviderInstance(
+                    certInstanceName,
+                    "cert-default",
+                    rootInstanceName,
+                    "root-default",
+                    alpnProtocols,
+                    staticCertValidationContext,
+                    requireClientCert);
+    return certProviderServerSslContextProviderFactory.getProvider(
+            downstreamTlsContext,
+            bootstrapInfo.getNode().toEnvoyProtoNode(),
+            bootstrapInfo.getCertProviders());
+  }
+
+
   @Test
   public void testProviderForServer_mtls() throws Exception {
     final CertificateProvider.DistributorWatcher[] watcherCaptor =
@@ -146,6 +172,74 @@
   }
 
   @Test
+  public void testProviderForServer_mtls_newXds() throws Exception {
+    final CertificateProvider.DistributorWatcher[] watcherCaptor =
+            new CertificateProvider.DistributorWatcher[1];
+    TestCertificateProvider.createAndRegisterProviderProvider(
+            certificateProviderRegistry, watcherCaptor, "testca", 0);
+    CertificateValidationContext staticCertValidationContext =
+        CertificateValidationContext.newBuilder().addAllMatchSubjectAltNames(Arrays
+            .asList(StringMatcher.newBuilder().setExact("foo.com").build(),
+                StringMatcher.newBuilder().setExact("bar.com").build())).build();
+    CertProviderServerSslContextProvider provider =
+            getNewSslContextProvider(
+                    "gcp_id",
+                    "gcp_id",
+                    CommonBootstrapperTestUtils.getTestBootstrapInfo(),
+                    /* alpnProtocols= */ null,
+                    staticCertValidationContext,
+                    /* requireClientCert= */ true);
+
+    assertThat(provider.savedKey).isNull();
+    assertThat(provider.savedCertChain).isNull();
+    assertThat(provider.savedTrustedRoots).isNull();
+    assertThat(provider.getSslContext()).isNull();
+
+    // now generate cert update
+    watcherCaptor[0].updateCertificate(
+            CommonCertProviderTestUtils.getPrivateKey(SERVER_0_KEY_FILE),
+            ImmutableList.of(getCertFromResourceName(SERVER_0_PEM_FILE)));
+    assertThat(provider.savedKey).isNotNull();
+    assertThat(provider.savedCertChain).isNotNull();
+    assertThat(provider.getSslContext()).isNull();
+
+    // now generate root cert update
+    watcherCaptor[0].updateTrustedRoots(ImmutableList.of(getCertFromResourceName(CA_PEM_FILE)));
+    assertThat(provider.getSslContext()).isNotNull();
+    assertThat(provider.savedKey).isNull();
+    assertThat(provider.savedCertChain).isNull();
+    assertThat(provider.savedTrustedRoots).isNull();
+
+    TestCallback testCallback =
+            CommonTlsContextTestsUtil.getValueThruCallback(provider);
+
+    doChecksOnSslContext(true, testCallback.updatedSslContext, /* expectedApnProtos= */ null);
+    TestCallback testCallback1 =
+            CommonTlsContextTestsUtil.getValueThruCallback(provider);
+    assertThat(testCallback1.updatedSslContext).isSameInstanceAs(testCallback.updatedSslContext);
+
+    // just do root cert update: sslContext should still be the same
+    watcherCaptor[0].updateTrustedRoots(
+            ImmutableList.of(getCertFromResourceName(CLIENT_PEM_FILE)));
+    assertThat(provider.savedKey).isNull();
+    assertThat(provider.savedCertChain).isNull();
+    assertThat(provider.savedTrustedRoots).isNotNull();
+    testCallback1 = CommonTlsContextTestsUtil.getValueThruCallback(provider);
+    assertThat(testCallback1.updatedSslContext).isSameInstanceAs(testCallback.updatedSslContext);
+
+    // now update id cert: sslContext should be updated i.e.different from the previous one
+    watcherCaptor[0].updateCertificate(
+            CommonCertProviderTestUtils.getPrivateKey(SERVER_1_KEY_FILE),
+            ImmutableList.of(getCertFromResourceName(SERVER_1_PEM_FILE)));
+    assertThat(provider.savedKey).isNull();
+    assertThat(provider.savedCertChain).isNull();
+    assertThat(provider.savedTrustedRoots).isNull();
+    assertThat(provider.getSslContext()).isNotNull();
+    testCallback1 = CommonTlsContextTestsUtil.getValueThruCallback(provider);
+    assertThat(testCallback1.updatedSslContext).isNotSameInstanceAs(testCallback.updatedSslContext);
+  }
+
+  @Test
   public void testProviderForServer_queueExecutor() throws Exception {
     final CertificateProvider.DistributorWatcher[] watcherCaptor =
         new CertificateProvider.DistributorWatcher[1];
diff --git a/xds/src/test/java/io/grpc/xds/internal/sds/CommonTlsContextTestsUtil.java b/xds/src/test/java/io/grpc/xds/internal/sds/CommonTlsContextTestsUtil.java
index 81fbda9..840cced 100644
--- a/xds/src/test/java/io/grpc/xds/internal/sds/CommonTlsContextTestsUtil.java
+++ b/xds/src/test/java/io/grpc/xds/internal/sds/CommonTlsContextTestsUtil.java
@@ -22,6 +22,7 @@
 import com.google.common.io.CharStreams;
 import com.google.common.util.concurrent.MoreExecutors;
 import com.google.protobuf.BoolValue;
+import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateProviderPluginInstance;
 import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext;
 import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext;
 import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext.CertificateProviderInstance;
@@ -234,6 +235,30 @@
     return builder.build();
   }
 
+  private static CommonTlsContext buildNewCommonTlsContextForCertProviderInstance(
+          String certInstanceName,
+          String certName,
+          String rootInstanceName,
+          String rootCertName,
+          Iterable<String> alpnProtocols,
+          CertificateValidationContext staticCertValidationContext) {
+    CommonTlsContext.Builder builder = CommonTlsContext.newBuilder();
+    if (certInstanceName != null) {
+      builder =
+              builder.setTlsCertificateProviderInstance(
+                      CertificateProviderPluginInstance.newBuilder()
+                              .setInstanceName(certInstanceName)
+                              .setCertificateName(certName));
+    }
+    builder =
+            addNewCertificateValidationContext(
+                    builder, rootInstanceName, rootCertName, staticCertValidationContext);
+    if (alpnProtocols != null) {
+      builder.addAllAlpnProtocols(alpnProtocols);
+    }
+    return builder.build();
+  }
+
   @SuppressWarnings("deprecation")
   private static CommonTlsContext.Builder addCertificateValidationContext(
       CommonTlsContext.Builder builder,
@@ -259,6 +284,26 @@
     return builder;
   }
 
+  private static CommonTlsContext.Builder addNewCertificateValidationContext(
+          CommonTlsContext.Builder builder,
+          String rootInstanceName,
+          String rootCertName,
+          CertificateValidationContext staticCertValidationContext) {
+    if (rootInstanceName != null) {
+      CertificateProviderPluginInstance providerInstance =
+          CertificateProviderPluginInstance.newBuilder()
+              .setInstanceName(rootInstanceName)
+              .setCertificateName(rootCertName)
+              .build();
+      CertificateValidationContext.Builder validationContextBuilder =
+          staticCertValidationContext != null ? staticCertValidationContext.toBuilder()
+              : CertificateValidationContext.newBuilder();
+      return builder.setValidationContext(
+          validationContextBuilder.setCaCertificateProviderInstance(providerInstance));
+    }
+    return builder;
+  }
+
   /** Helper method to build UpstreamTlsContext for CertProvider tests. */
   public static EnvoyServerProtoData.UpstreamTlsContext
       buildUpstreamTlsContextForCertProviderInstance(
@@ -278,6 +323,25 @@
             staticCertValidationContext));
   }
 
+  /** Helper method to build UpstreamTlsContext for CertProvider tests. */
+  public static EnvoyServerProtoData.UpstreamTlsContext
+      buildNewUpstreamTlsContextForCertProviderInstance(
+          @Nullable String certInstanceName,
+          @Nullable String certName,
+          @Nullable String rootInstanceName,
+          @Nullable String rootCertName,
+          Iterable<String> alpnProtocols,
+          CertificateValidationContext staticCertValidationContext) {
+    return buildUpstreamTlsContext(
+        buildNewCommonTlsContextForCertProviderInstance(
+            certInstanceName,
+            certName,
+            rootInstanceName,
+            rootCertName,
+            alpnProtocols,
+            staticCertValidationContext));
+  }
+
   /** Helper method to build DownstreamTlsContext for CertProvider tests. */
   public static EnvoyServerProtoData.DownstreamTlsContext
       buildDownstreamTlsContextForCertProviderInstance(
@@ -298,6 +362,25 @@
             staticCertValidationContext), requireClientCert);
   }
 
+  /** Helper method to build DownstreamTlsContext for CertProvider tests. */
+  public static EnvoyServerProtoData.DownstreamTlsContext
+      buildNewDownstreamTlsContextForCertProviderInstance(
+          @Nullable String certInstanceName,
+          @Nullable String certName,
+          @Nullable String rootInstanceName,
+          @Nullable String rootCertName,
+          Iterable<String> alpnProtocols,
+          CertificateValidationContext staticCertValidationContext,
+          boolean requireClientCert) {
+    return buildInternalDownstreamTlsContext(
+            buildNewCommonTlsContextForCertProviderInstance(
+                    certInstanceName,
+                    certName,
+                    rootInstanceName,
+                    rootCertName,
+                    alpnProtocols,
+                    staticCertValidationContext), requireClientCert);
+  }
 
   /** Perform some simple checks on sslContext. */
   public static void doChecksOnSslContext(boolean server, SslContext sslContext,