All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.grpc.TlsServerCredentials Maven / Gradle / Ivy

/*
 * 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.base.Preconditions;
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 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; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy