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

com.youtube.vitess.client.grpc.GrpcClientFactory Maven / Gradle / Ivy

The newest version!
package com.youtube.vitess.client.grpc;

import com.youtube.vitess.client.Context;
import com.youtube.vitess.client.RpcClient;
import com.youtube.vitess.client.RpcClientFactory;

import com.youtube.vitess.client.grpc.tls.TlsOptions;
import io.grpc.netty.GrpcSslContexts;
import io.grpc.netty.NegotiationType;
import io.grpc.netty.NettyChannelBuilder;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;

import javax.net.ssl.SSLException;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.UnrecoverableEntryException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Enumeration;

/**
 * GrpcClientFactory creates RpcClients with the gRPC implementation.
 */
public class GrpcClientFactory implements RpcClientFactory {

  /**
   * Factory method to construct a gRPC client connection with no transport-layer security.
   *
   * @param ctx TODO: This parameter is not actually used, but probably SHOULD be so that timeout duration and caller ID settings aren't discarded
   * @param address
   * @return
   */
  @Override
  public RpcClient create(Context ctx, InetSocketAddress address) {
    return new GrpcClient(
            NettyChannelBuilder.forAddress(address).negotiationType(NegotiationType.PLAINTEXT).build());
  }

  /**
   * 

Factory method to construct a gRPC client connection with transport-layer security.

* *

Within the tlsOptions parameter value, the trustStore field should * always be populated. All other fields are optional.

* * @param ctx TODO: This parameter is not actually used, but probably SHOULD be so that timeout duration and caller ID settings aren't discarded * @param address * @param tlsOptions * @return */ @Override public RpcClient createTls(Context ctx, InetSocketAddress address, TlsOptions tlsOptions) { final SslContextBuilder sslContextBuilder = GrpcSslContexts.forClient(); // trustManager should always be set final KeyStore trustStore = loadKeyStore(tlsOptions.getTrustStore(), tlsOptions.getTrustStorePassword()); if (trustStore == null) { throw new RuntimeException("Could not load trustStore"); } final X509Certificate[] trustCertCollection = tlsOptions.getTrustAlias() == null ? loadCertCollection(trustStore) : loadCertCollectionForAlias(trustStore, tlsOptions.getTrustAlias()); sslContextBuilder.trustManager(trustCertCollection); // keyManager should only be set if a keyStore is specified (meaning that client authentication is enabled) final KeyStore keyStore = loadKeyStore(tlsOptions.getKeyStore(), tlsOptions.getKeyStorePassword()); if (keyStore != null) { final PrivateKeyWrapper privateKeyWrapper = tlsOptions.getKeyAlias() == null ? loadPrivateKeyEntry(keyStore, tlsOptions.getKeyStorePassword(), tlsOptions.getKeyPassword()) : loadPrivateKeyEntryForAlias(keyStore, tlsOptions.getKeyAlias(), tlsOptions.getKeyStorePassword(), tlsOptions.getKeyPassword()); if (privateKeyWrapper == null) { throw new RuntimeException("Could not retrieve private key and certificate chain from keyStore"); } sslContextBuilder.keyManager( privateKeyWrapper.getPrivateKey(), privateKeyWrapper.getPassword(), privateKeyWrapper.getCertificateChain() ); } final SslContext sslContext; try { sslContext = sslContextBuilder.build(); } catch (SSLException e) { throw new RuntimeException(e); } return new GrpcClient( NettyChannelBuilder.forAddress(address).negotiationType(NegotiationType.TLS).sslContext(sslContext).build()); } /** *

Opens a JKS keystore file from the filesystem.

* *

Returns null if the file is inaccessible for any reason, or if the password fails to unlock it.

* * @param keyStoreFile * @param keyStorePassword * @return * @throws KeyStoreException * @throws IOException * @throws CertificateException * @throws NoSuchAlgorithmException */ private KeyStore loadKeyStore(final File keyStoreFile, String keyStorePassword) { if (keyStoreFile == null) { return null; } try { final KeyStore keyStore = KeyStore.getInstance(Constants.KEYSTORE_TYPE); final char[] password = keyStorePassword == null ? null : keyStorePassword.toCharArray(); try (final FileInputStream fis = new FileInputStream(keyStoreFile)) { keyStore.load(fis, password); } return keyStore; } catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException e) { return null; } } /** *

Loads an X509 certificate from a keystore using a given alias, and returns it as a one-element * array so that it can be passed to {@link SslContextBuilder#trustManager(File)}.

* *

Returns null if there is any problem accessing the keystore, or if the alias does * not match an X509 certificate.

* * @param keyStore * @param alias * @return */ private X509Certificate[] loadCertCollectionForAlias(final KeyStore keyStore, final String alias) { if (keyStore == null) { return null; } try { return new X509Certificate[] { (X509Certificate) keyStore.getCertificate(alias) }; } catch (KeyStoreException | ClassCastException e) { return null; } } /** *

Loads the first valid X509 certificate found in the keystore, and returns it as a one-element * array so that it can be passed to {@link SslContextBuilder#trustManager(File)}.

* *

Returns null if there is any problem accessing the keystore, or if no X509 certificates * are found at all.

* * @param keyStore * @return */ private X509Certificate[] loadCertCollection(final KeyStore keyStore) { if (keyStore == null) { return null; } final Enumeration aliases; try { aliases = keyStore.aliases(); } catch (KeyStoreException e) { return null; } while (aliases.hasMoreElements()) { final String alias = aliases.nextElement(); final X509Certificate[] certCollection = loadCertCollectionForAlias(keyStore, alias); if (certCollection != null) { return certCollection; } } return null; } /** *

Loads from a keystore the private key entry matching a given alias, and returns it parsed and * ready to be passed to {@link SslContextBuilder#keyManager(PrivateKey, String, X509Certificate...)}.

* *

To access the private key, this method will first try using the keyPassword parameter * value (this parameter can be set to null to explicitly indicate that there is no password). * If that fails, then this method will then try using the keyStorePassword parameter value * as the key value too.

* *

Returns null if there is any problem accessing the keystore, if neither keyPassword * or keyStorePassword can unlock the private key, or if no private key entry matches * alias.

* * @param keyStore * @param alias * @param keyStorePassword * @param keyPassword * @return * @throws KeyStoreException * @throws NoSuchAlgorithmException * @throws UnrecoverableEntryException */ private PrivateKeyWrapper loadPrivateKeyEntryForAlias(final KeyStore keyStore, final String alias, final String keyStorePassword, final String keyPassword) { if (keyStore == null || alias == null) { return null; } try { if (!keyStore.entryInstanceOf(alias, KeyStore.PrivateKeyEntry.class)) { // There is no private key matching this alias return null; } } catch (KeyStoreException e) { return null; } // Try loading the private key with the key password (which can be null) try { final char[] pass = keyPassword == null ? null : keyPassword.toCharArray(); final KeyStore.PrivateKeyEntry entry = (KeyStore.PrivateKeyEntry) keyStore.getEntry(alias, new KeyStore.PasswordProtection(pass)); return new PrivateKeyWrapper(entry.getPrivateKey(), keyPassword, entry.getCertificateChain()); } catch (KeyStoreException | NoSuchAlgorithmException e) { return null; } catch (UnrecoverableEntryException e) { // The key password didn't work (or just wasn't set). Try using the keystore password. final char[] pass = keyStorePassword == null ? null : keyStorePassword.toCharArray(); try { final KeyStore.PrivateKeyEntry entry = (KeyStore.PrivateKeyEntry) keyStore.getEntry(alias, new KeyStore.PasswordProtection(pass)); return new PrivateKeyWrapper(entry.getPrivateKey(), keyPassword, entry.getCertificateChain()); } catch (KeyStoreException | NoSuchAlgorithmException | UnrecoverableEntryException e1) { // Neither password worked. return null; } } } /** *

Loads from a keystore the first valid private key entry found, and returns it parsed and ready to be * passed to {@link SslContextBuilder#keyManager(PrivateKey, String, X509Certificate...)}.

* *

To access the private key, this method will first try using the keyPassword parameter * value (this parameter can be set to null to explicitly indicate that there is no password). * If that fails, then this method will then try using the keyStorePassword parameter value * as the key value too.

* *

Returns null if there is any problem accessing the keystore, if neither keyPassword * or keyStorePassword can unlock the private key, or if no private key entry matches * alias.

* * @param keyStore * @param keyStorePassword * @param keyPassword * @return * @throws KeyStoreException * @throws NoSuchAlgorithmException */ private PrivateKeyWrapper loadPrivateKeyEntry(final KeyStore keyStore, final String keyStorePassword, final String keyPassword) { if (keyStore == null) { return null; } final Enumeration aliases; try { aliases = keyStore.aliases(); } catch (KeyStoreException e) { return null; } while (aliases.hasMoreElements()) { final String alias = aliases.nextElement(); final PrivateKeyWrapper privateKeyWrapper = loadPrivateKeyEntryForAlias(keyStore, alias, keyStorePassword, keyPassword); if (privateKeyWrapper != null) { return privateKeyWrapper; } } return null; } /** * A container for the values returned by {@link this#loadPrivateKeyEntry(KeyStore, String, String)} and * {@link this#loadPrivateKeyEntryForAlias(KeyStore, String, String, String)}, and passed in turn * to {@link SslContextBuilder#keyManager(PrivateKey, String, X509Certificate...)}. Helpful since Java * methods cannot return multiple values. */ private class PrivateKeyWrapper { private PrivateKey privateKey; private String password; private X509Certificate[] certificateChain; public PrivateKeyWrapper(final PrivateKey privateKey, final String password, final Certificate[] certificateChain) { this.privateKey = privateKey; this.password = password; this.certificateChain = Arrays.copyOf(certificateChain, certificateChain.length, X509Certificate[].class); } public PrivateKey getPrivateKey() { return privateKey; } public String getPassword() { return password; } public X509Certificate[] getCertificateChain() { return certificateChain; } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy