1 /*
2  * Copyright 2020 The gRPC Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package io.grpc.xds.internal.security.certprovider;
18 
19 import static com.google.common.base.Preconditions.checkNotNull;
20 
21 import com.google.common.annotations.VisibleForTesting;
22 import io.grpc.Status;
23 import io.grpc.xds.internal.security.Closeable;
24 import java.security.PrivateKey;
25 import java.security.cert.X509Certificate;
26 import java.util.Collections;
27 import java.util.HashSet;
28 import java.util.List;
29 import java.util.Set;
30 
31 /**
32  * A plug-in that provides certificates required by the xDS security component and created
33  * using the certificate-provider config from the xDS server.
34  *
35  * <p>We may move this out of the internal package and make this an official API in the future.
36  *
37  * <p>The plugin fetches certificates - root and optionally identity cert - required by xDS
38  * security.
39  */
40 public abstract class CertificateProvider implements Closeable {
41 
42   /** A watcher is registered to receive certificate updates. */
43   public interface Watcher {
updateCertificate(PrivateKey key, List<X509Certificate> certChain)44     void updateCertificate(PrivateKey key, List<X509Certificate> certChain);
45 
updateTrustedRoots(List<X509Certificate> trustedRoots)46     void updateTrustedRoots(List<X509Certificate> trustedRoots);
47 
onError(Status errorStatus)48     void onError(Status errorStatus);
49   }
50 
51   @VisibleForTesting
52   public static final class DistributorWatcher implements Watcher {
53     private PrivateKey privateKey;
54     private List<X509Certificate> certChain;
55     private List<X509Certificate> trustedRoots;
56 
57     @VisibleForTesting
58     final Set<Watcher> downstreamWatchers = new HashSet<>();
59 
addWatcher(Watcher watcher)60     synchronized void addWatcher(Watcher watcher) {
61       downstreamWatchers.add(watcher);
62       if (privateKey != null && certChain != null) {
63         sendLastCertificateUpdate(watcher);
64       }
65       if (trustedRoots != null) {
66         sendLastTrustedRootsUpdate(watcher);
67       }
68     }
69 
removeWatcher(Watcher watcher)70     synchronized void removeWatcher(Watcher watcher) {
71       downstreamWatchers.remove(watcher);
72     }
73 
getDownstreamWatchers()74     @VisibleForTesting public Set<Watcher> getDownstreamWatchers() {
75       return Collections.unmodifiableSet(downstreamWatchers);
76     }
77 
sendLastCertificateUpdate(Watcher watcher)78     private void sendLastCertificateUpdate(Watcher watcher) {
79       watcher.updateCertificate(privateKey, certChain);
80     }
81 
sendLastTrustedRootsUpdate(Watcher watcher)82     private void sendLastTrustedRootsUpdate(Watcher watcher) {
83       watcher.updateTrustedRoots(trustedRoots);
84     }
85 
86     @Override
updateCertificate(PrivateKey key, List<X509Certificate> certChain)87     public synchronized void updateCertificate(PrivateKey key, List<X509Certificate> certChain) {
88       checkNotNull(key, "key");
89       checkNotNull(certChain, "certChain");
90       privateKey = key;
91       this.certChain = certChain;
92       for (Watcher watcher : downstreamWatchers) {
93         sendLastCertificateUpdate(watcher);
94       }
95     }
96 
97     @Override
updateTrustedRoots(List<X509Certificate> trustedRoots)98     public synchronized void updateTrustedRoots(List<X509Certificate> trustedRoots) {
99       checkNotNull(trustedRoots, "trustedRoots");
100       this.trustedRoots = trustedRoots;
101       for (Watcher watcher : downstreamWatchers) {
102         sendLastTrustedRootsUpdate(watcher);
103       }
104     }
105 
106     @Override
onError(Status errorStatus)107     public synchronized void onError(Status errorStatus) {
108       for (Watcher watcher : downstreamWatchers) {
109         watcher.onError(errorStatus);
110       }
111     }
112 
getLastIdentityCert()113     X509Certificate getLastIdentityCert() {
114       if (certChain != null && !certChain.isEmpty()) {
115         return certChain.get(0);
116       }
117       return null;
118     }
119 
close()120     void close() {
121       downstreamWatchers.clear();
122       clearValues();
123     }
124 
clearValues()125     void clearValues() {
126       privateKey = null;
127       certChain = null;
128       trustedRoots = null;
129     }
130   }
131 
132   /**
133    * Concrete subclasses will call this to register the {@link Watcher}.
134    *
135    * @param watcher to register
136    * @param notifyCertUpdates if true, the provider is required to call the watcher’s
137    *     updateCertificate method. Implies the Provider is capable of minting certificates.
138    *     Used by server-side and mTLS client-side. Note the Provider is always required
139    *     to call updateTrustedRoots to provide trusted-root updates.
140    */
CertificateProvider(DistributorWatcher watcher, boolean notifyCertUpdates)141   protected CertificateProvider(DistributorWatcher watcher, boolean notifyCertUpdates) {
142     this.watcher = watcher;
143     this.notifyCertUpdates = notifyCertUpdates;
144   }
145 
146   /** Releases all resources and stop cert refreshes and watcher updates. */
147   @Override
close()148   public abstract void close();
149 
150   /** Starts the cert refresh and watcher update cycle. */
start()151   public abstract void start();
152 
153   private final DistributorWatcher watcher;
154   private final boolean notifyCertUpdates;
155 
getWatcher()156   public DistributorWatcher getWatcher() {
157     return watcher;
158   }
159 
isNotifyCertUpdates()160   public boolean isNotifyCertUpdates() {
161     return notifyCertUpdates;
162   }
163 
164 
165 }
166