/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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.undertow.server.handlers; import io.undertow.UndertowLogger; import io.undertow.server.BasicSSLSessionInfo; import io.undertow.server.ExchangeCompletionListener; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.SSLSessionInfo; import io.undertow.server.handlers.builder.HandlerBuilder; import io.undertow.util.Certificates; import io.undertow.util.HeaderMap; import javax.security.cert.CertificateException; import static io.undertow.util.Headers.SSL_CIPHER; import static io.undertow.util.Headers.SSL_CLIENT_CERT; import static io.undertow.util.Headers.SSL_SESSION_ID; import java.util.Collections; import java.util.Map; import java.util.Set; /** * Handler that sets SSL information on the connection based on the following headers: * <p> * <ul> * <li>SSL_CLIENT_CERT</li> * <li>SSL_CIPHER</li> * <li>SSL_SESSION_ID</li> * </ul> * <p> * If this handler is present in the chain it will always override the SSL session information, * even if these headers are not present. * <p> * This handler MUST only be used on servers that are behind a reverse proxy, where the reverse proxy * has been configured to always set these header for EVERY request (or strip existing headers with these * names if no SSL information is present). Otherwise it may be possible for a malicious client to spoof * a SSL connection. * * @author Stuart Douglas */ public class SSLHeaderHandler implements HttpHandler { public static final String HTTPS = "https"; private static final ExchangeCompletionListener CLEAR_SSL_LISTENER = new ExchangeCompletionListener() { @Override public void exchangeEvent(HttpServerExchange exchange, NextListener nextListener) { exchange.getConnection().setSslSessionInfo(null); nextListener.proceed(); } }; private final HttpHandler next; public SSLHeaderHandler(HttpHandler next) { this.next = next; } @Override public void handleRequest(HttpServerExchange exchange) throws Exception { HeaderMap requestHeaders = exchange.getRequestHeaders(); final String sessionId = requestHeaders.getFirst(SSL_SESSION_ID); final String cipher = requestHeaders.getFirst(SSL_CIPHER); String clientCert = requestHeaders.getFirst(SSL_CLIENT_CERT); //the proxy client replaces \n with ' ' if (clientCert != null && clientCert.length() > 28) { StringBuilder sb = new StringBuilder(clientCert.length() + 1); sb.append(Certificates.BEGIN_CERT); sb.append('\n'); sb.append(clientCert.replace(' ', '\n').substring(28, clientCert.length() - 26));//core certificate data sb.append('\n'); sb.append(Certificates.END_CERT); clientCert = sb.toString(); } if (clientCert != null || sessionId != null || cipher != null) { try { SSLSessionInfo info = new BasicSSLSessionInfo(sessionId, cipher, clientCert); exchange.setRequestScheme(HTTPS); exchange.getConnection().setSslSessionInfo(info); exchange.addExchangeCompleteListener(CLEAR_SSL_LISTENER); } catch (java.security.cert.CertificateException | CertificateException e) { UndertowLogger.REQUEST_LOGGER.debugf(e, "Could not create certificate from header %s", clientCert); } } next.handleRequest(exchange); } public static class Builder implements HandlerBuilder { @Override public String name() { return "ssl-headers"; } @Override public Map<String, Class<?>> parameters() { return Collections.emptyMap(); } @Override public Set<String> requiredParameters() { return Collections.emptySet(); } @Override public String defaultParameter() { return null; } @Override public HandlerWrapper build(Map<String, Object> config) { return new Wrapper(); } } private static class Wrapper implements HandlerWrapper { @Override public HttpHandler wrap(HttpHandler handler) { return new SSLHeaderHandler(handler); } } }