forked from grpc/grpc-java
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathTlsChannelCredentials.java
More file actions
368 lines (338 loc) · 14.6 KB
/
TlsChannelCredentials.java
File metadata and controls
368 lines (338 loc) · 14.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
/*
* 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;
import com.google.common.io.ByteStreams;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import javax.net.ssl.KeyManager;
import javax.net.ssl.TrustManager;
/**
* TLS credentials, providing server authentication and encryption. Consumers of this credential
* must verify they understand the configuration via the {@link #incomprehensible
* incomprehensible()} method. Unless overridden by a {@code Feature}, server verification should
* use customary default root certificates.
*/
public final class TlsChannelCredentials extends ChannelCredentials {
/** Use TLS with its defaults. */
public static ChannelCredentials create() {
return newBuilder().build();
}
private final boolean fakeFeature;
private final byte[] certificateChain;
private final byte[] privateKey;
private final String privateKeyPassword;
private final List<KeyManager> keyManagers;
private final byte[] rootCertificates;
private final List<TrustManager> trustManagers;
TlsChannelCredentials(Builder builder) {
fakeFeature = builder.fakeFeature;
certificateChain = builder.certificateChain;
privateKey = builder.privateKey;
privateKeyPassword = builder.privateKeyPassword;
keyManagers = builder.keyManagers;
rootCertificates = builder.rootCertificates;
trustManagers = builder.trustManagers;
}
/**
* The certificate chain for the client's identity, as a new byte array. Generally should be
* PEM-encoded. If {@code null}, some feature is providing key manager information via a different
* method or no client identity is available.
*/
public byte[] getCertificateChain() {
if (certificateChain == null) {
return null;
}
return Arrays.copyOf(certificateChain, certificateChain.length);
}
/**
* The private key for the client's identity, as a new byte array. Generally should be in PKCS#8
* format. If encrypted, {@link #getPrivateKeyPassword} is the decryption key. If unencrypted, the
* password will be {@code null}. If {@code null}, some feature is providing key manager
* information via a different method or no client identity is available.
*/
public byte[] getPrivateKey() {
if (privateKey == null) {
return null;
}
return Arrays.copyOf(privateKey, privateKey.length);
}
/** Returns the password to decrypt the private key, or {@code null} if unencrypted. */
public String getPrivateKeyPassword() {
return privateKeyPassword;
}
/**
* Returns the key manager list which provides the client's identity. Entries are scanned checking
* for specific types, like {@link javax.net.ssl.X509KeyManager}. Only a single entry for a type
* is used. Entries earlier in the list are higher priority. If {@code null}, key manager
* information is provided via a different method or no client identity is available.
*/
public List<KeyManager> getKeyManagers() {
return keyManagers;
}
/**
* Root trust certificates for verifying the server's identity that override the system's
* defaults. Generally PEM-encoded with multiple certificates concatenated.
*/
public byte[] getRootCertificates() {
if (rootCertificates == null) {
return null;
}
return Arrays.copyOf(rootCertificates, rootCertificates.length);
}
/**
* Returns the trust manager list which verifies the server's identity. Entries are scanned
* checking for specific types, like {@link javax.net.ssl.X509TrustManager}. Only a single entry
* for a type is used. Entries earlier in the list are higher priority. If {@code null}, trust
* manager information is provided via the system's default or a different method.
*/
public List<TrustManager> getTrustManagers() {
return trustManagers;
}
/**
* Returns an empty set if this credential can be adequately understood via
* the features listed, otherwise returns a hint of features that are lacking
* to understand the configuration to be used for manual debugging.
*
* <p>An "understood" feature does not imply the caller is able to fully
* handle the feature. It simply means the caller understands the feature
* enough to use the appropriate APIs to read the configuration. The caller
* may support just a subset of a feature, in which case the caller would
* need to look at the configuration to determine if only the supported
* subset is used.
*
* <p>This method may not be as simple as a set difference. There may be
* multiple features that can independently satisfy a piece of configuration.
* If the configuration is incomprehensible, all such features would be
* returned, even though only one may be necessary.
*
* <p>An empty set does not imply that the credentials are fully understood.
* There may be optional configuration that can be ignored if not understood.
*
* <p>Since {@code Feature} is an {@code enum}, {@code understoodFeatures}
* should generally be an {@link java.util.EnumSet}. {@code
* understoodFeatures} will not be modified.
*
* @param understoodFeatures the features understood by the caller
* @return empty set if the caller can adequately understand the configuration
*/
public Set<Feature> incomprehensible(Set<Feature> understoodFeatures) {
Set<Feature> incomprehensible = EnumSet.noneOf(Feature.class);
if (fakeFeature) {
requiredFeature(understoodFeatures, incomprehensible, Feature.FAKE);
}
if (rootCertificates != null || privateKey != null || keyManagers != null) {
requiredFeature(understoodFeatures, incomprehensible, Feature.MTLS);
}
if (keyManagers != null || trustManagers != null) {
requiredFeature(understoodFeatures, incomprehensible, Feature.CUSTOM_MANAGERS);
}
return Collections.unmodifiableSet(incomprehensible);
}
private static void requiredFeature(
Set<Feature> understoodFeatures, Set<Feature> incomprehensible, Feature feature) {
if (!understoodFeatures.contains(feature)) {
incomprehensible.add(feature);
}
}
@Override
public ChannelCredentials withoutBearerTokens() {
return this;
}
/**
* Features to understand TLS configuration. Additional enum values may be added in the future.
*/
public enum Feature {
/**
* A feature that no consumer should understand. It should be used for unit testing to confirm
* a call to {@link #incomprehensible incomprehensible()} is implemented properly.
*/
FAKE,
/**
* Client identity may be provided and server verification can be tuned. This feature requires
* observing {@link #getCertificateChain}, {@link #getPrivateKey}, and {@link
* #getPrivateKeyPassword} as well as {@link #getRootCertificates()}. The certificate chain and
* private key are used to configure a key manager to provide the client's identity. If no
* certificate chain and private key are provided the client will have no identity. The root
* certificates are used to configure a trust manager for verifying the server's identity. If no
* root certificates are provided the trust manager will default to the system's root
* certificates.
*/
MTLS,
/**
* Key managers and trust managers may be specified as {@link KeyManager} and {@link
* TrustManager} objects. This feature requires observing {@link #getKeyManagers()} and {@link
* #getTrustManagers()}. Generally {@link #MTLS} should also be supported, as that is the more
* common method of configuration. When a manager is non-{@code null}, then it is wholly
* responsible for key or trust material and usage; there is no need to check other manager
* sources like {@link #getCertificateChain()} or {@link #getPrivateKey()} (if {@code
* KeyManager} is available), or {@link #getRootCertificates()} (if {@code TrustManager} is
* available).
*
* <p>If other manager sources are available (e.g., {@code getPrivateKey() != null}), then they
* may be alternative representations of the same configuration and the consumer is free to use
* those alternative representations if it prefers. But before doing so it <em>must</em> first
* check that it understands that alternative representation by using {@link #incomprehensible}
* <em>without</em> the {@code CUSTOM_MANAGERS} feature.
*/
CUSTOM_MANAGERS,
;
}
/** Creates a builder for changing default configuration. */
public static Builder newBuilder() {
return new Builder();
}
/** Builder for {@link TlsChannelCredentials}. */
public static final class Builder {
private boolean fakeFeature;
private byte[] certificateChain;
private byte[] privateKey;
private String privateKeyPassword;
private List<KeyManager> keyManagers;
private byte[] rootCertificates;
private List<TrustManager> trustManagers;
private Builder() {}
/**
* Requires {@link Feature#FAKE} to be understood. For use in testing consumers of this
* credential.
*/
public Builder requireFakeFeature() {
fakeFeature = true;
return this;
}
/**
* Use the provided certificate chain and private key as the client's identity. Generally they
* should be PEM-encoded and the key is an unencrypted PKCS#8 key (file headers have "BEGIN
* CERTIFICATE" and "BEGIN PRIVATE KEY").
*/
public Builder keyManager(File certChain, File privateKey) throws IOException {
return keyManager(certChain, privateKey, null);
}
/**
* Use the provided certificate chain and possibly-encrypted private key as the client's
* identity. Generally they should be PEM-encoded and the key is a PKCS#8 key. If the private
* key is unencrypted, then password must be {@code null}.
*/
public Builder keyManager(File certChain, File privateKey, String privateKeyPassword)
throws IOException {
InputStream certChainIs = new FileInputStream(certChain);
try {
InputStream privateKeyIs = new FileInputStream(privateKey);
try {
return keyManager(certChainIs, privateKeyIs, privateKeyPassword);
} finally {
privateKeyIs.close();
}
} finally {
certChainIs.close();
}
}
/**
* Use the provided certificate chain and private key as the client's identity. Generally they
* should be PEM-encoded and the key is an unencrypted PKCS#8 key (file headers have "BEGIN
* CERTIFICATE" and "BEGIN PRIVATE KEY").
*/
public Builder keyManager(InputStream certChain, InputStream privateKey) throws IOException {
return keyManager(certChain, privateKey, null);
}
/**
* Use the provided certificate chain and possibly-encrypted private key as the client's
* identity. Generally they should be PEM-encoded and the key is a PKCS#8 key. If the private
* key is unencrypted, then password must be {@code null}.
*/
public Builder keyManager(
InputStream certChain, InputStream privateKey, String privateKeyPassword)
throws IOException {
byte[] certChainBytes = ByteStreams.toByteArray(certChain);
byte[] privateKeyBytes = ByteStreams.toByteArray(privateKey);
clearKeyManagers();
this.certificateChain = certChainBytes;
this.privateKey = privateKeyBytes;
this.privateKeyPassword = privateKeyPassword;
return this;
}
/**
* Have the provided key manager select the client's identity. Although multiple are allowed,
* only the first instance implementing a particular interface is used. So generally there will
* just be a single entry and it implements {@link javax.net.ssl.X509KeyManager}.
*/
public Builder keyManager(KeyManager... keyManagers) {
List<KeyManager> keyManagerList = Collections.unmodifiableList(new ArrayList<>(
Arrays.asList(keyManagers)));
clearKeyManagers();
this.keyManagers = keyManagerList;
return this;
}
private void clearKeyManagers() {
this.certificateChain = null;
this.privateKey = null;
this.privateKeyPassword = null;
this.keyManagers = null;
}
/**
* Use the provided root certificates to verify the server's identity instead of the system's
* default. Generally they should be PEM-encoded with all the certificates concatenated together
* (file header has "BEGIN CERTIFICATE", and would occur once per certificate).
*/
public Builder trustManager(File rootCerts) throws IOException {
InputStream rootCertsIs = new FileInputStream(rootCerts);
try {
return trustManager(rootCertsIs);
} finally {
rootCertsIs.close();
}
}
/**
* Use the provided root certificates to verify the server's identity instead of the system's
* default. Generally they should be PEM-encoded with all the certificates concatenated together
* (file header has "BEGIN CERTIFICATE", and would occur once per certificate).
*/
public Builder trustManager(InputStream rootCerts) throws IOException {
byte[] rootCertsBytes = ByteStreams.toByteArray(rootCerts);
clearTrustManagers();
this.rootCertificates = rootCertsBytes;
return this;
}
/**
* Have the provided trust manager verify the server's identity instead of the system's default.
* Although multiple are allowed, only the first instance implementing a particular interface is
* used. So generally there will just be a single entry and it implements {@link
* javax.net.ssl.X509TrustManager}.
*/
public Builder trustManager(TrustManager... trustManagers) {
List<TrustManager> trustManagerList = Collections.unmodifiableList(new ArrayList<>(
Arrays.asList(trustManagers)));
clearTrustManagers();
this.trustManagers = trustManagerList;
return this;
}
private void clearTrustManagers() {
this.rootCertificates = null;
this.trustManagers = null;
}
/** Construct the credentials. */
public ChannelCredentials build() {
return new TlsChannelCredentials(this);
}
}
}