Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
4076998
Save changes.
kannanjgithub Sep 8, 2025
968d564
Save changes.
kannanjgithub Sep 8, 2025
4cf653d
Merge branch 'master' into systemrootcerts-ignore-trusted-root-updates
kannanjgithub Sep 8, 2025
6c1898a
Save changes.
kannanjgithub Sep 8, 2025
5be2aa2
Save changes.
kannanjgithub Sep 8, 2025
90abe55
style
kannanjgithub Sep 9, 2025
4c44e4c
Add comment and rename some confusing method names.
kannanjgithub Sep 9, 2025
ae74a9e
Merge branch 'clientsidenormaltls-systemrootcert-handle' into systemr…
kannanjgithub Sep 9, 2025
199cc69
style.
kannanjgithub Sep 9, 2025
37cd044
Handle Sslcontext updates for System root certs with and without Mtls.
kannanjgithub Sep 10, 2025
139805e
Style changes.
kannanjgithub Sep 10, 2025
7f48afa
Remove special-casing for System root certs in SslContextProviderSupp…
kannanjgithub Sep 11, 2025
d2b722a
Formatting changes.
kannanjgithub Sep 11, 2025
e95725d
Trust manager handling for system root certs.
kannanjgithub Sep 11, 2025
180f373
Fix style
kannanjgithub Sep 11, 2025
381beb2
Fixes.
kannanjgithub Sep 11, 2025
18f5d5a
Fix unit tests to cover both mtls and non-mtls for system root certs.
kannanjgithub Sep 11, 2025
e18d6cd
Suppress warning.
kannanjgithub Sep 12, 2025
3845e16
Use non wildcard SAN in the SAN matchers in validation context.
kannanjgithub Sep 12, 2025
f135943
xds: Plumb system root certs similarly to CertProviders
ejona86 Sep 22, 2025
0d5eb0a
Fix certs not updated for handshake.
kannanjgithub Sep 23, 2025
08391fb
Merge branch 'master' into systemrootcerts-ignore-trusted-root-updates
kannanjgithub Sep 23, 2025
d6acdfc
Merge branch 'ejona86_xds_system_cert' into systemrootcerts-ignore-tr…
kannanjgithub Sep 23, 2025
c14a488
More fixes for system root certs.
kannanjgithub Sep 23, 2025
733f57c
More fixes for system root certs.
kannanjgithub Sep 23, 2025
967fe8c
Address review comment to remove reundant if block
kannanjgithub Sep 24, 2025
0edfb2f
Address review comments.
kannanjgithub Sep 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -55,20 +55,19 @@ protected final SslContextBuilder getSslContextBuilder(
CertificateValidationContext certificateValidationContextdationContext)
throws CertStoreException {
SslContextBuilder sslContextBuilder = GrpcSslContexts.forClient();
// Null rootCertInstance implies hasSystemRootCerts because of the check in
// CertProviderClientSslContextProviderFactory.
if (rootCertInstance != null) {
if (savedSpiffeTrustMap != null) {
sslContextBuilder = sslContextBuilder.trustManager(
if (savedSpiffeTrustMap != null) {
sslContextBuilder = sslContextBuilder.trustManager(
new XdsTrustManagerFactory(
savedSpiffeTrustMap,
certificateValidationContextdationContext));
} else if (savedTrustedRoots != null) {
sslContextBuilder = sslContextBuilder.trustManager(
new XdsTrustManagerFactory(
savedSpiffeTrustMap,
savedTrustedRoots.toArray(new X509Certificate[0]),
certificateValidationContextdationContext));
} else {
sslContextBuilder = sslContextBuilder.trustManager(
new XdsTrustManagerFactory(
savedTrustedRoots.toArray(new X509Certificate[0]),
certificateValidationContextdationContext));
}
} else {
// Should be impossible because of the check in CertProviderClientSslContextProviderFactory
throw new IllegalStateException("There must be trusted roots or a SPIFFE trust map");
}
if (isMtls()) {
sslContextBuilder.keyManager(savedKey, savedCertChain);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import io.grpc.xds.client.Bootstrapper.CertificateProviderInfo;
import io.grpc.xds.internal.security.CommonTlsContextUtil;
import io.grpc.xds.internal.security.DynamicSslContextProvider;
import java.io.Closeable;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.List;
Expand All @@ -34,8 +35,8 @@
abstract class CertProviderSslContextProvider extends DynamicSslContextProvider implements
CertificateProvider.Watcher {

@Nullable private final CertificateProviderStore.Handle certHandle;
@Nullable private final CertificateProviderStore.Handle rootCertHandle;
@Nullable private final NoExceptionCloseable certHandle;
@Nullable private final NoExceptionCloseable rootCertHandle;
@Nullable private final CertificateProviderInstance certInstance;
@Nullable protected final CertificateProviderInstance rootCertInstance;
@Nullable protected PrivateKey savedKey;
Expand All @@ -55,38 +56,50 @@ protected CertProviderSslContextProvider(
super(tlsContext, staticCertValidationContext);
this.certInstance = certInstance;
this.rootCertInstance = rootCertInstance;
String certInstanceName = null;
if (certInstance != null && certInstance.isInitialized()) {
certInstanceName = certInstance.getInstanceName();
this.isUsingSystemRootCerts = rootCertInstance == null
&& CommonTlsContextUtil.isUsingSystemRootCerts(tlsContext.getCommonTlsContext());
boolean createCertInstance = certInstance != null && certInstance.isInitialized();
boolean createRootCertInstance = rootCertInstance != null && rootCertInstance.isInitialized();
boolean sharedCertInstance = createCertInstance && createRootCertInstance
&& rootCertInstance.getInstanceName().equals(certInstance.getInstanceName());
if (createCertInstance) {
CertificateProviderInfo certProviderInstanceConfig =
getCertProviderConfig(certProviders, certInstanceName);
getCertProviderConfig(certProviders, certInstance.getInstanceName());
CertificateProvider.Watcher watcher = this;
if (!sharedCertInstance && !isUsingSystemRootCerts) {
watcher = new IgnoreUpdatesWatcher(watcher, /* ignoreRootCertUpdates= */ true);
}
// TODO: Previously we'd hang if certProviderInstanceConfig were null or
// certInstance.isInitialized() == false. Now we'll proceed. Those should be errors, or are
// they impossible and should be assertions?
certHandle = certProviderInstanceConfig == null ? null
: certificateProviderStore.createOrGetProvider(
certInstance.getCertificateName(),
certProviderInstanceConfig.pluginName(),
certProviderInstanceConfig.config(),
this,
true);
watcher,
true)::close;
} else {
certHandle = null;
}
if (rootCertInstance != null
&& rootCertInstance.isInitialized()
&& !rootCertInstance.getInstanceName().equals(certInstanceName)) {
if (createRootCertInstance && !sharedCertInstance) {
CertificateProviderInfo certProviderInstanceConfig =
getCertProviderConfig(certProviders, rootCertInstance.getInstanceName());
rootCertHandle = certProviderInstanceConfig == null ? null
: certificateProviderStore.createOrGetProvider(
rootCertInstance.getCertificateName(),
certProviderInstanceConfig.pluginName(),
certProviderInstanceConfig.config(),
this,
true);
new IgnoreUpdatesWatcher(this, /* ignoreRootCertUpdates= */ false),
false)::close;
} else if (rootCertInstance == null
&& CommonTlsContextUtil.isUsingSystemRootCerts(tlsContext.getCommonTlsContext())) {
SystemRootCertificateProvider systemRootProvider = new SystemRootCertificateProvider(this);
systemRootProvider.start();
rootCertHandle = systemRootProvider::close;
} else {
rootCertHandle = null;
}
this.isUsingSystemRootCerts = rootCertInstance == null
&& CommonTlsContextUtil.isUsingSystemRootCerts(tlsContext.getCommonTlsContext());
}

private static CertificateProviderInfo getCertProviderConfig(
Expand Down Expand Up @@ -150,17 +163,16 @@ public final void updateSpiffeTrustMap(Map<String, List<X509Certificate>> spiffe

private void updateSslContextWhenReady() {
if (isMtls()) {
if (savedKey != null
&& (savedTrustedRoots != null || isUsingSystemRootCerts || savedSpiffeTrustMap != null)) {
if (savedKey != null && (savedTrustedRoots != null || savedSpiffeTrustMap != null)) {
updateSslContext();
clearKeysAndCerts();
}
} else if (isClientSideTls()) {
} else if (isRegularTlsAndClientSide()) {
if (savedTrustedRoots != null || savedSpiffeTrustMap != null) {
updateSslContext();
clearKeysAndCerts();
}
} else if (isServerSideTls()) {
} else if (isRegularTlsAndServerSide()) {
if (savedKey != null) {
updateSslContext();
clearKeysAndCerts();
Expand All @@ -170,20 +182,22 @@ private void updateSslContextWhenReady() {

private void clearKeysAndCerts() {
savedKey = null;
savedTrustedRoots = null;
savedSpiffeTrustMap = null;
if (!isUsingSystemRootCerts) {
savedTrustedRoots = null;
savedSpiffeTrustMap = null;
}
savedCertChain = null;
}

protected final boolean isMtls() {
return certInstance != null && (rootCertInstance != null || isUsingSystemRootCerts);
}

protected final boolean isClientSideTls() {
return rootCertInstance != null && certInstance == null;
protected final boolean isRegularTlsAndClientSide() {
return (rootCertInstance != null || isUsingSystemRootCerts) && certInstance == null;
}

protected final boolean isServerSideTls() {
protected final boolean isRegularTlsAndServerSide() {
return certInstance != null && rootCertInstance == null;
}

Expand All @@ -201,4 +215,9 @@ public final void close() {
rootCertHandle.close();
}
}

interface NoExceptionCloseable extends Closeable {
@Override
void close();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright 2025 The gRPC Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.grpc.xds.internal.security.certprovider;

import static java.util.Objects.requireNonNull;

import com.google.common.annotations.VisibleForTesting;
import io.grpc.Status;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.Map;

public final class IgnoreUpdatesWatcher implements CertificateProvider.Watcher {
private final CertificateProvider.Watcher delegate;
private final boolean ignoreRootCertUpdates;

public IgnoreUpdatesWatcher(
CertificateProvider.Watcher delegate, boolean ignoreRootCertUpdates) {
this.delegate = requireNonNull(delegate, "delegate");
this.ignoreRootCertUpdates = ignoreRootCertUpdates;
}

@Override
public void updateCertificate(PrivateKey key, List<X509Certificate> certChain) {
if (ignoreRootCertUpdates) {
delegate.updateCertificate(key, certChain);
}
}

@Override
public void updateTrustedRoots(List<X509Certificate> trustedRoots) {
if (!ignoreRootCertUpdates) {
delegate.updateTrustedRoots(trustedRoots);
}
}

@Override
public void updateSpiffeTrustMap(Map<String, List<X509Certificate>> spiffeTrustMap) {
if (!ignoreRootCertUpdates) {
delegate.updateSpiffeTrustMap(spiffeTrustMap);
}
}

@Override
public void onError(Status errorStatus) {
delegate.onError(errorStatus);
}

@VisibleForTesting
public CertificateProvider.Watcher getDelegate() {
return delegate;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright 2020 The gRPC Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.grpc.xds.internal.security.certprovider;

import io.grpc.Status;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;

/**
* An non-registered provider for CertProviderSslContextProvider to use the same code path for
* system root certs as provider-obtained certs.
*/
final class SystemRootCertificateProvider extends CertificateProvider {
public SystemRootCertificateProvider(CertificateProvider.Watcher watcher) {
super(new DistributorWatcher(), false);
getWatcher().addWatcher(watcher);
}

@Override
public void start() {
try {
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null);

List<TrustManager> trustManagers = Arrays.asList(trustManagerFactory.getTrustManagers());
List<X509Certificate> rootCerts = trustManagers.stream()
.filter(X509TrustManager.class::isInstance)
.map(X509TrustManager.class::cast)
.map(trustManager -> Arrays.asList(trustManager.getAcceptedIssuers()))
.flatMap(Collection::stream)
.collect(Collectors.toList());
getWatcher().updateTrustedRoots(rootCerts);
} catch (KeyStoreException | NoSuchAlgorithmException ex) {
getWatcher().onError(Status.UNAVAILABLE
.withDescription("Could not load system root certs")
.withCause(ex));
}
}

@Override
public void close() {
// Unnecessary because there's no more callbacks, but do it for good measure
for (Watcher watcher : getWatcher().getDownstreamWatchers()) {
getWatcher().removeWatcher(watcher);
}
}
}
Loading