BCJsseServerALPNProcessor.java

/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF 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 org.apache.cxf.transport.http_jetty;

import javax.net.ssl.SSLEngine;

import org.eclipse.jetty.alpn.server.ALPNServerConnection;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.ssl.ALPNProcessor;
import org.eclipse.jetty.io.ssl.SslConnection;
import org.eclipse.jetty.io.ssl.SslHandshakeListener;

/**
 * Jetty {@link ALPNProcessor.Server} for BouncyCastle JSSE ({@code bctls-jdk18on}).
 *
 * <p>Jetty's built-in {@code JDK9ServerALPNProcessor} only matches engines whose
 * module is {@code java.base}, so it rejects BC JSSE's {@code ProvSSLEngine} and
 * throws {@code IllegalStateException: No suitable ALPNProcessor}.  This processor
 * bridges the gap: it claims BC JSSE engines and wires up the same
 * {@link SSLEngine#setHandshakeApplicationProtocolSelector} callback that the
 * built-in processor uses, delegating to {@link ALPNServerConnection#select}.
 *
 * <p>A {@link SslHandshakeListener} is also registered so that if the client does not
 * advertise any ALPN protocols, {@link ALPNServerConnection#unsupported()} is called
 * on handshake success to fall back to the server's default protocol (HTTP/1.1).
 *
 * <p>Registered via {@code META-INF/services/org.eclipse.jetty.io.ssl.ALPNProcessor$Server}.
 */
public class BCJsseServerALPNProcessor implements ALPNProcessor.Server {

    @Override
    public boolean appliesTo(SSLEngine sslEngine) {
        return sslEngine.getClass().getName().startsWith("org.bouncycastle.");
    }

    @Override
    public void configure(SSLEngine sslEngine, Connection connection) {
        ALPNServerConnection alpnConn = (ALPNServerConnection) connection;

        // Called when the client includes an ALPN extension in its ClientHello.
        sslEngine.setHandshakeApplicationProtocolSelector((engine, protocols) -> {
            try {
                alpnConn.select(protocols);
                return alpnConn.getProtocol();
            } catch (Throwable t) {
                return null;
            }
        });

        // When the client sends no ALPN extension the selector above is never invoked,
        // leaving getProtocol() null.  The SslHandshakeListener fires on handshake
        // completion and calls unsupported() to fall back to the default protocol.
        SslConnection.SslEndPoint sslEndPoint = (SslConnection.SslEndPoint) alpnConn.getEndPoint();
        sslEndPoint.getSslConnection().addHandshakeListener(new SslHandshakeListener() {
            @Override
            public void handshakeSucceeded(Event event) {
                if (alpnConn.getProtocol() == null) {
                    alpnConn.unsupported();
                }
            }
        });
    }
}