/* * Copyright 2018 LinkedIn Corp. All rights reserved. * * 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. */ package com.github.ambry.commons; import com.github.ambry.config.SSLConfig; import io.netty.buffer.ByteBufAllocator; import io.netty.handler.codec.http2.Http2SecurityUtil; import io.netty.handler.ssl.ApplicationProtocolConfig; import io.netty.handler.ssl.ApplicationProtocolNames; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.SupportedCipherSuiteFilter; import io.netty.handler.ssl.util.InsecureTrustManagerFactory; import io.netty.handler.ssl.util.SelfSignedCertificate; import java.io.IOException; import java.security.GeneralSecurityException; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLParameters; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * An HTTP/2 specific implementation of {@link SSLFactory} that uses Netty's SSL libraries for HTTP2. * The main differences between this factory and {@link NettySslFactory} are getServerSslContext and getServerClientContext. * This factory provides HTTP2 specific sslContext: HTTP2 Ciphers and ALPN. * */ public class NettySslHttp2Factory implements SSLFactory { private static final Logger logger = LoggerFactory.getLogger(NettySslFactory.class); private final SslContext nettyServerSslContext; private final SslContext nettyClientSslContext; private final String endpointIdentification; /** * Instantiate a {@link NettySslFactory} from a config. * @param sslConfig the {@link SSLConfig} to use. * @throws GeneralSecurityException * @throws IOException */ public NettySslHttp2Factory(SSLConfig sslConfig) throws GeneralSecurityException, IOException { nettyServerSslContext = getServerSslContext(sslConfig); nettyClientSslContext = getClientSslContext(sslConfig); // Netty's OpenSsl based implementation does not use the JDK SSLContext so we have to fall back to the JDK based // factory to support this method. this.endpointIdentification = sslConfig.sslEndpointIdentificationAlgorithm.isEmpty() ? null : sslConfig.sslEndpointIdentificationAlgorithm; } @Override public SSLEngine createSSLEngine(String peerHost, int peerPort, Mode mode) { SslContext context = mode == Mode.CLIENT ? nettyClientSslContext : nettyServerSslContext; SSLEngine sslEngine = context.newEngine(ByteBufAllocator.DEFAULT, peerHost, peerPort); if (mode == Mode.CLIENT) { SSLParameters sslParams = sslEngine.getSSLParameters(); sslParams.setEndpointIdentificationAlgorithm(endpointIdentification); sslEngine.setSSLParameters(sslParams); } return sslEngine; } @Override public SSLContext getSSLContext() { throw new UnsupportedOperationException(); } /** * @param config the {@link SSLConfig} * @return a configured {@link SslContext} object for a client. * @throws GeneralSecurityException * @throws IOException */ static SslContext getServerSslContext(SSLConfig config) throws GeneralSecurityException, IOException { logger.info("Using {} provider for server SslContext", SslContext.defaultServerProvider()); SslContextBuilder sslContextBuilder; if (config.sslHttp2SelfSign) { SelfSignedCertificate ssc = new SelfSignedCertificate(); sslContextBuilder = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()); logger.info("Using Self Signed Certificate."); } else { sslContextBuilder = SslContextBuilder.forServer(NettySslFactory.getKeyManagerFactory(config)) .trustManager(NettySslFactory.getTrustManagerFactory(config)); } return sslContextBuilder.sslProvider(SslContext.defaultClientProvider()) .clientAuth(NettySslFactory.getClientAuth(config)) /* NOTE: the cipher filter may not include all ciphers required by the HTTP/2 specification. * Please refer to the HTTP/2 specification for cipher requirements. */ .ciphers(Http2SecurityUtil.CIPHERS, SupportedCipherSuiteFilter.INSTANCE) .applicationProtocolConfig(new ApplicationProtocolConfig(ApplicationProtocolConfig.Protocol.ALPN, // NO_ADVERTISE is currently the only mode supported by both OpenSsl and JDK providers. ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE, // ACCEPT is currently the only mode supported by both OpenSsl and JDK providers. ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT, ApplicationProtocolNames.HTTP_2)) .build(); } /** * @param config the {@link SSLConfig} * @return a configured {@link SslContext} object for a server. * @throws GeneralSecurityException * @throws IOException */ public static SslContext getClientSslContext(SSLConfig config) throws GeneralSecurityException, IOException { logger.info("Using {} provider for client ", SslContext.defaultClientProvider()); SslContextBuilder sslContextBuilder; if (config.sslHttp2SelfSign) { sslContextBuilder = SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE); logger.info("Using Self Signed Certificate."); } else { sslContextBuilder = SslContextBuilder.forClient() .keyManager(NettySslFactory.getKeyManagerFactory(config)) .trustManager(NettySslFactory.getTrustManagerFactory(config)); } return sslContextBuilder.sslProvider(SslContext.defaultClientProvider()) /* NOTE: the cipher filter may not include all ciphers required by the HTTP/2 specification. * Please refer to the HTTP/2 specification for cipher requirements. */ .ciphers(Http2SecurityUtil.CIPHERS, SupportedCipherSuiteFilter.INSTANCE) .applicationProtocolConfig(new ApplicationProtocolConfig(ApplicationProtocolConfig.Protocol.ALPN, // NO_ADVERTISE is currently the only mode supported by both OpenSsl and JDK providers. ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE, // ACCEPT is currently the only mode supported by both OpenSsl and JDK providers. ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT, ApplicationProtocolNames.HTTP_2)) .build(); } }