org.apache.ratis.thirdparty.io.grpc.TlsServerCredentials Maven / Gradle / Ivy
Show all versions of ratis-thirdparty-misc
/*
* 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 org.apache.ratis.thirdparty.io.grpc;
import org.apache.ratis.thirdparty.com.google.common.base.Preconditions;
import org.apache.ratis.thirdparty.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 identity 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 identity is provided via {@link
* #getCertificateChain}, {@link #getPrivateKey}, and {@link #getPrivateKeyPassword}.
*/
public final class TlsServerCredentials extends ServerCredentials {
/**
* Creates an instance using provided certificate chain and private key. 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 static ServerCredentials create(File certChain, File privateKey) throws IOException {
return newBuilder().keyManager(certChain, privateKey).build();
}
/**
* Creates an instance using provided certificate chain and private key. Generally they should be
* PEM-encoded and the key is an unencrypted PKCS#8 key (file headers have "BEGIN CERTIFICATE" and
* "BEGIN PRIVATE KEY").
*
* The streams will not be automatically closed.
*/
public static ServerCredentials create(
InputStream certChain, InputStream privateKey) throws IOException {
return newBuilder().keyManager(certChain, privateKey).build();
}
private final boolean fakeFeature;
private final byte[] certificateChain;
private final byte[] privateKey;
private final String privateKeyPassword;
private final List keyManagers;
private final ClientAuth clientAuth;
private final byte[] rootCertificates;
private final List trustManagers;
TlsServerCredentials(Builder builder) {
fakeFeature = builder.fakeFeature;
certificateChain = builder.certificateChain;
privateKey = builder.privateKey;
privateKeyPassword = builder.privateKeyPassword;
keyManagers = builder.keyManagers;
clientAuth = builder.clientAuth;
rootCertificates = builder.rootCertificates;
trustManagers = builder.trustManagers;
}
/**
* The certificate chain for the server'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.
*/
public byte[] getCertificateChain() {
if (certificateChain == null) {
return null;
}
return Arrays.copyOf(certificateChain, certificateChain.length);
}
/**
* The private key for the server'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.
*/
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 server'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.
*/
public List getKeyManagers() {
return keyManagers;
}
/** Non-{@code null} setting indicating whether the server should expect a client's identity. */
public ClientAuth getClientAuth() {
return clientAuth;
}
/**
* Root trust certificates for verifying the client'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 client'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 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.
*
* 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.
*
*
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.
*
*
An empty set does not imply that the credentials are fully understood.
* There may be optional configuration that can be ignored if not understood.
*
*
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 incomprehensible(Set understoodFeatures) {
Set incomprehensible = EnumSet.noneOf(Feature.class);
if (fakeFeature) {
requiredFeature(understoodFeatures, incomprehensible, Feature.FAKE);
}
if (clientAuth != ClientAuth.NONE) {
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 understoodFeatures, Set incomprehensible, Feature feature) {
if (!understoodFeatures.contains(feature)) {
incomprehensible.add(feature);
}
}
/**
* 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 certificates may be requested and verified. This feature requires observing {@link
* #getRootCertificates()} and {@link #getClientAuth()}. The root certificates are used to
* configure a trust manager for verifying the client'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 by itself only implies {@link #getKeyManagers()} needs to
* be observed. But along with {@link #MTLS}, then {@link #getTrustManagers()} needs to be
* observed as well. 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).
*
* 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 must first
* check that it understands that alternative representation by using {@link #incomprehensible}
* without the {@code CUSTOM_MANAGERS} feature.
*/
CUSTOM_MANAGERS,
;
}
/**
* Creates a builder for changing default configuration. There is no default key manager, so key
* material must be specified. The default trust manager uses the system's root certificates. By
* default no client authentication will occur.
*/
public static Builder newBuilder() {
return new Builder();
}
/** Builder for {@link TlsServerCredentials}. */
public static final class Builder {
private boolean fakeFeature;
private byte[] certificateChain;
private byte[] privateKey;
private String privateKeyPassword;
private List keyManagers;
private ClientAuth clientAuth = ClientAuth.NONE;
private byte[] rootCertificates;
private List 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 server'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 server'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 server'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 server'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 server'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 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;
}
/**
* Indicates whether the server should expect a client's identity. Must not be {@code null}.
* Defaults to {@link ClientAuth#NONE}.
*/
public Builder clientAuth(ClientAuth clientAuth) {
Preconditions.checkNotNull(clientAuth, "clientAuth");
this.clientAuth = clientAuth;
return this;
}
/**
* Use the provided root certificates to verify the client'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 client'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 client'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 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 ServerCredentials build() {
if (certificateChain == null && keyManagers == null) {
throw new IllegalStateException("A key manager is required");
}
return new TlsServerCredentials(this);
}
}
/** The level of authentication the server should expect from the client. */
public enum ClientAuth {
/** Clients will not present any identity. */
NONE,
/**
* Clients are requested to present their identity, but clients without identities are
* permitted.
*/
OPTIONAL,
/**
* Clients are requested to present their identity, and are required to provide a valid
* identity.
*/
REQUIRE;
}
}