/* * Copyright 2015 The Netty Project * * The Netty Project licenses this file to you 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.netty.handler.ssl; import static io.netty.util.internal.ObjectUtil.checkNotNull; import io.netty.util.internal.UnstableApi; import java.security.Provider; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLException; import javax.net.ssl.TrustManagerFactory; import java.io.File; import java.io.InputStream; import java.security.PrivateKey; import java.security.cert.X509Certificate; import javax.net.ssl.SSLEngine; /** * Builder for configuring a new SslContext for creation. */ public final class SslContextBuilder { /** * Creates a builder for new client-side {@link SslContext}. */ public static SslContextBuilder forClient() { return new SslContextBuilder(false); } /** * Creates a builder for new server-side {@link SslContext}. * * @param keyCertChainFile an X.509 certificate chain file in PEM format * @param keyFile a PKCS#8 private key file in PEM format * @see #keyManager(File, File) */ public static SslContextBuilder forServer(File keyCertChainFile, File keyFile) { return new SslContextBuilder(true).keyManager(keyCertChainFile, keyFile); } /** * Creates a builder for new server-side {@link SslContext}. * * @param keyCertChainInputStream an input stream for an X.509 certificate chain in PEM format * @param keyInputStream an input stream for a PKCS#8 private key in PEM format * @see #keyManager(InputStream, InputStream) */ public static SslContextBuilder forServer(InputStream keyCertChainInputStream, InputStream keyInputStream) { return new SslContextBuilder(true).keyManager(keyCertChainInputStream, keyInputStream); } /** * Creates a builder for new server-side {@link SslContext}. * * @param key a PKCS#8 private key * @param keyCertChain the X.509 certificate chain * @see #keyManager(PrivateKey, X509Certificate[]) */ public static SslContextBuilder forServer(PrivateKey key, X509Certificate... keyCertChain) { return new SslContextBuilder(true).keyManager(key, keyCertChain); } /** * Creates a builder for new server-side {@link SslContext}. * * @param keyCertChainFile an X.509 certificate chain file in PEM format * @param keyFile a PKCS#8 private key file in PEM format * @param keyPassword the password of the {@code keyFile}, or {@code null} if it's not * password-protected * @see #keyManager(File, File, String) */ public static SslContextBuilder forServer( File keyCertChainFile, File keyFile, String keyPassword) { return new SslContextBuilder(true).keyManager(keyCertChainFile, keyFile, keyPassword); } /** * Creates a builder for new server-side {@link SslContext}. * * @param keyCertChainInputStream an input stream for an X.509 certificate chain in PEM format * @param keyInputStream an input stream for a PKCS#8 private key in PEM format * @param keyPassword the password of the {@code keyFile}, or {@code null} if it's not * password-protected * @see #keyManager(InputStream, InputStream, String) */ public static SslContextBuilder forServer( InputStream keyCertChainInputStream, InputStream keyInputStream, String keyPassword) { return new SslContextBuilder(true).keyManager(keyCertChainInputStream, keyInputStream, keyPassword); } /** * Creates a builder for new server-side {@link SslContext}. * * @param key a PKCS#8 private key * @param keyCertChain the X.509 certificate chain * @param keyPassword the password of the {@code keyFile}, or {@code null} if it's not * password-protected * @see #keyManager(File, File, String) */ public static SslContextBuilder forServer( PrivateKey key, String keyPassword, X509Certificate... keyCertChain) { return new SslContextBuilder(true).keyManager(key, keyPassword, keyCertChain); } /** * Creates a builder for new server-side {@link SslContext}. * * @param keyManagerFactory non-{@code null} factory for server's private key * @see #keyManager(KeyManagerFactory) */ public static SslContextBuilder forServer(KeyManagerFactory keyManagerFactory) { return new SslContextBuilder(true).keyManager(keyManagerFactory); } private final boolean forServer; private SslProvider provider; private Provider sslContextProvider; private X509Certificate[] trustCertCollection; private TrustManagerFactory trustManagerFactory; private X509Certificate[] keyCertChain; private PrivateKey key; private String keyPassword; private KeyManagerFactory keyManagerFactory; private Iterable<String> ciphers; private CipherSuiteFilter cipherFilter = IdentityCipherSuiteFilter.INSTANCE; private ApplicationProtocolConfig apn; private long sessionCacheSize; private long sessionTimeout; private ClientAuth clientAuth = ClientAuth.NONE; private String[] protocols; private boolean startTls; private boolean enableOcsp; private SslContextBuilder(boolean forServer) { this.forServer = forServer; } /** * The {@link SslContext} implementation to use. {@code null} uses the default one. */ public SslContextBuilder sslProvider(SslProvider provider) { this.provider = provider; return this; } /** * The SSLContext {@link Provider} to use. {@code null} uses the default one. This is only * used with {@link SslProvider#JDK}. */ public SslContextBuilder sslContextProvider(Provider sslContextProvider) { this.sslContextProvider = sslContextProvider; return this; } /** * Trusted certificates for verifying the remote endpoint's certificate. The file should * contain an X.509 certificate collection in PEM format. {@code null} uses the system default. */ public SslContextBuilder trustManager(File trustCertCollectionFile) { try { return trustManager(SslContext.toX509Certificates(trustCertCollectionFile)); } catch (Exception e) { throw new IllegalArgumentException("File does not contain valid certificates: " + trustCertCollectionFile, e); } } /** * Trusted certificates for verifying the remote endpoint's certificate. The input stream should * contain an X.509 certificate collection in PEM format. {@code null} uses the system default. */ public SslContextBuilder trustManager(InputStream trustCertCollectionInputStream) { try { return trustManager(SslContext.toX509Certificates(trustCertCollectionInputStream)); } catch (Exception e) { throw new IllegalArgumentException("Input stream does not contain valid certificates.", e); } } /** * Trusted certificates for verifying the remote endpoint's certificate, {@code null} uses the system default. */ public SslContextBuilder trustManager(X509Certificate... trustCertCollection) { this.trustCertCollection = trustCertCollection != null ? trustCertCollection.clone() : null; trustManagerFactory = null; return this; } /** * Trusted manager for verifying the remote endpoint's certificate. {@code null} uses the system default. */ public SslContextBuilder trustManager(TrustManagerFactory trustManagerFactory) { trustCertCollection = null; this.trustManagerFactory = trustManagerFactory; return this; } /** * Identifying certificate for this host. {@code keyCertChainFile} and {@code keyFile} may * be {@code null} for client contexts, which disables mutual authentication. * * @param keyCertChainFile an X.509 certificate chain file in PEM format * @param keyFile a PKCS#8 private key file in PEM format */ public SslContextBuilder keyManager(File keyCertChainFile, File keyFile) { return keyManager(keyCertChainFile, keyFile, null); } /** * Identifying certificate for this host. {@code keyCertChainInputStream} and {@code keyInputStream} may * be {@code null} for client contexts, which disables mutual authentication. * * @param keyCertChainInputStream an input stream for an X.509 certificate chain in PEM format * @param keyInputStream an input stream for a PKCS#8 private key in PEM format */ public SslContextBuilder keyManager(InputStream keyCertChainInputStream, InputStream keyInputStream) { return keyManager(keyCertChainInputStream, keyInputStream, null); } /** * Identifying certificate for this host. {@code keyCertChain} and {@code key} may * be {@code null} for client contexts, which disables mutual authentication. * * @param key a PKCS#8 private key * @param keyCertChain an X.509 certificate chain */ public SslContextBuilder keyManager(PrivateKey key, X509Certificate... keyCertChain) { return keyManager(key, null, keyCertChain); } /** * Identifying certificate for this host. {@code keyCertChainFile} and {@code keyFile} may * be {@code null} for client contexts, which disables mutual authentication. * * @param keyCertChainFile an X.509 certificate chain file in PEM format * @param keyFile a PKCS#8 private key file in PEM format * @param keyPassword the password of the {@code keyFile}, or {@code null} if it's not * password-protected */ public SslContextBuilder keyManager(File keyCertChainFile, File keyFile, String keyPassword) { X509Certificate[] keyCertChain; PrivateKey key; try { keyCertChain = SslContext.toX509Certificates(keyCertChainFile); } catch (Exception e) { throw new IllegalArgumentException("File does not contain valid certificates: " + keyCertChainFile, e); } try { key = SslContext.toPrivateKey(keyFile, keyPassword); } catch (Exception e) { throw new IllegalArgumentException("File does not contain valid private key: " + keyFile, e); } return keyManager(key, keyPassword, keyCertChain); } /** * Identifying certificate for this host. {@code keyCertChainInputStream} and {@code keyInputStream} may * be {@code null} for client contexts, which disables mutual authentication. * * @param keyCertChainInputStream an input stream for an X.509 certificate chain in PEM format * @param keyInputStream an input stream for a PKCS#8 private key in PEM format * @param keyPassword the password of the {@code keyInputStream}, or {@code null} if it's not * password-protected */ public SslContextBuilder keyManager(InputStream keyCertChainInputStream, InputStream keyInputStream, String keyPassword) { X509Certificate[] keyCertChain; PrivateKey key; try { keyCertChain = SslContext.toX509Certificates(keyCertChainInputStream); } catch (Exception e) { throw new IllegalArgumentException("Input stream not contain valid certificates.", e); } try { key = SslContext.toPrivateKey(keyInputStream, keyPassword); } catch (Exception e) { throw new IllegalArgumentException("Input stream does not contain valid private key.", e); } return keyManager(key, keyPassword, keyCertChain); } /** * Identifying certificate for this host. {@code keyCertChain} and {@code key} may * be {@code null} for client contexts, which disables mutual authentication. * * @param key a PKCS#8 private key file * @param keyPassword the password of the {@code key}, or {@code null} if it's not * password-protected * @param keyCertChain an X.509 certificate chain */ public SslContextBuilder keyManager(PrivateKey key, String keyPassword, X509Certificate... keyCertChain) { if (forServer) { checkNotNull(keyCertChain, "keyCertChain required for servers"); if (keyCertChain.length == 0) { throw new IllegalArgumentException("keyCertChain must be non-empty"); } checkNotNull(key, "key required for servers"); } if (keyCertChain == null || keyCertChain.length == 0) { this.keyCertChain = null; } else { for (X509Certificate cert: keyCertChain) { if (cert == null) { throw new IllegalArgumentException("keyCertChain contains null entry"); } } this.keyCertChain = keyCertChain.clone(); } this.key = key; this.keyPassword = keyPassword; keyManagerFactory = null; return this; } /** * Identifying manager for this host. {@code keyManagerFactory} may be {@code null} for * client contexts, which disables mutual authentication. Using a {@link KeyManagerFactory} * is only supported for {@link SslProvider#JDK} or {@link SslProvider#OPENSSL} / {@link SslProvider#OPENSSL_REFCNT} * if the used openssl version is 1.0.1+. You can check if your openssl version supports using a * {@link KeyManagerFactory} by calling {@link OpenSsl#supportsKeyManagerFactory()}. If this is not the case * you must use {@link #keyManager(File, File)} or {@link #keyManager(File, File, String)}. */ public SslContextBuilder keyManager(KeyManagerFactory keyManagerFactory) { if (forServer) { checkNotNull(keyManagerFactory, "keyManagerFactory required for servers"); } keyCertChain = null; key = null; keyPassword = null; this.keyManagerFactory = keyManagerFactory; return this; } /** * The cipher suites to enable, in the order of preference. {@code null} to use default * cipher suites. */ public SslContextBuilder ciphers(Iterable<String> ciphers) { return ciphers(ciphers, IdentityCipherSuiteFilter.INSTANCE); } /** * The cipher suites to enable, in the order of preference. {@code cipherFilter} will be * applied to the ciphers before use. If {@code ciphers} is {@code null}, then the default * cipher suites will be used. */ public SslContextBuilder ciphers(Iterable<String> ciphers, CipherSuiteFilter cipherFilter) { checkNotNull(cipherFilter, "cipherFilter"); this.ciphers = ciphers; this.cipherFilter = cipherFilter; return this; } /** * Application protocol negotiation configuration. {@code null} disables support. */ public SslContextBuilder applicationProtocolConfig(ApplicationProtocolConfig apn) { this.apn = apn; return this; } /** * Set the size of the cache used for storing SSL session objects. {@code 0} to use the * default value. */ public SslContextBuilder sessionCacheSize(long sessionCacheSize) { this.sessionCacheSize = sessionCacheSize; return this; } /** * Set the timeout for the cached SSL session objects, in seconds. {@code 0} to use the * default value. */ public SslContextBuilder sessionTimeout(long sessionTimeout) { this.sessionTimeout = sessionTimeout; return this; } /** * Sets the client authentication mode. */ public SslContextBuilder clientAuth(ClientAuth clientAuth) { this.clientAuth = checkNotNull(clientAuth, "clientAuth"); return this; } /** * The TLS protocol versions to enable. * @param protocols The protocols to enable, or {@code null} to enable the default protocols. * @see SSLEngine#setEnabledCipherSuites(String[]) */ public SslContextBuilder protocols(String... protocols) { this.protocols = protocols == null ? null : protocols.clone(); return this; } /** * {@code true} if the first write request shouldn't be encrypted. */ public SslContextBuilder startTls(boolean startTls) { this.startTls = startTls; return this; } /** * Enables OCSP stapling. Please note that not all {@link SslProvider} implementations support OCSP * stapling and an exception will be thrown upon {@link #build()}. * * @see OpenSsl#isOcspSupported() */ @UnstableApi public SslContextBuilder enableOcsp(boolean enableOcsp) { this.enableOcsp = enableOcsp; return this; } /** * Create new {@code SslContext} instance with configured settings. * <p>If {@link #sslProvider(SslProvider)} is set to {@link SslProvider#OPENSSL_REFCNT} then the caller is * responsible for releasing this object, or else native memory may leak. */ public SslContext build() throws SSLException { if (forServer) { return SslContext.newServerContextInternal(provider, sslContextProvider, trustCertCollection, trustManagerFactory, keyCertChain, key, keyPassword, keyManagerFactory, ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout, clientAuth, protocols, startTls, enableOcsp); } else { return SslContext.newClientContextInternal(provider, sslContextProvider, trustCertCollection, trustManagerFactory, keyCertChain, key, keyPassword, keyManagerFactory, ciphers, cipherFilter, apn, protocols, sessionCacheSize, sessionTimeout, enableOcsp); } } }