191 lines
7.5 KiB
Java
191 lines
7.5 KiB
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.catalina.valves;
|
|
|
|
import java.io.ByteArrayInputStream;
|
|
import java.io.IOException;
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.security.NoSuchProviderException;
|
|
import java.security.cert.CertificateFactory;
|
|
import java.security.cert.X509Certificate;
|
|
|
|
import javax.servlet.ServletException;
|
|
|
|
import org.apache.catalina.Globals;
|
|
import org.apache.catalina.connector.Request;
|
|
import org.apache.catalina.connector.Response;
|
|
import org.apache.juli.logging.Log;
|
|
import org.apache.juli.logging.LogFactory;
|
|
|
|
/**
|
|
* When using mod_proxy_http, the client SSL information is not included in the
|
|
* protocol (unlike mod_jk and mod_proxy_ajp). To make the client SSL
|
|
* information available to Tomcat, some additional configuration is required.
|
|
* In httpd, mod_headers is used to add the SSL information as HTTP headers. In
|
|
* Tomcat, this valve is used to read the information from the HTTP headers and
|
|
* insert it into the request.<p>
|
|
*
|
|
* <b>Note: Ensure that the headers are always set by httpd for all requests to
|
|
* prevent a client spoofing SSL information by sending fake headers. </b><p>
|
|
*
|
|
* In httpd.conf add the following:
|
|
* <pre>
|
|
* <IfModule ssl_module>
|
|
* RequestHeader set SSL_CLIENT_CERT "%{SSL_CLIENT_CERT}s"
|
|
* RequestHeader set SSL_CIPHER "%{SSL_CIPHER}s"
|
|
* RequestHeader set SSL_SESSION_ID "%{SSL_SESSION_ID}s"
|
|
* RequestHeader set SSL_CIPHER_USEKEYSIZE "%{SSL_CIPHER_USEKEYSIZE}s"
|
|
* </IfModule>
|
|
* </pre>
|
|
*
|
|
* In server.xml, configure this valve under the Engine element in server.xml:
|
|
* <pre>
|
|
* <Engine ...>
|
|
* <Valve className="org.apache.catalina.valves.SSLValve" />
|
|
* <Host ... />
|
|
* </Engine>
|
|
* </pre>
|
|
*/
|
|
public class SSLValve extends ValveBase {
|
|
|
|
private static final Log log = LogFactory.getLog(SSLValve.class);
|
|
|
|
private String sslClientCertHeader = "ssl_client_cert";
|
|
private String sslCipherHeader = "ssl_cipher";
|
|
private String sslSessionIdHeader = "ssl_session_id";
|
|
private String sslCipherUserKeySizeHeader = "ssl_cipher_usekeysize";
|
|
|
|
//------------------------------------------------------ Constructor
|
|
public SSLValve() {
|
|
super(true);
|
|
}
|
|
|
|
|
|
public String getSslClientCertHeader() {
|
|
return sslClientCertHeader;
|
|
}
|
|
|
|
public void setSslClientCertHeader(String sslClientCertHeader) {
|
|
this.sslClientCertHeader = sslClientCertHeader;
|
|
}
|
|
|
|
public String getSslCipherHeader() {
|
|
return sslCipherHeader;
|
|
}
|
|
|
|
public void setSslCipherHeader(String sslCipherHeader) {
|
|
this.sslCipherHeader = sslCipherHeader;
|
|
}
|
|
|
|
public String getSslSessionIdHeader() {
|
|
return sslSessionIdHeader;
|
|
}
|
|
|
|
public void setSslSessionIdHeader(String sslSessionIdHeader) {
|
|
this.sslSessionIdHeader = sslSessionIdHeader;
|
|
}
|
|
|
|
public String getSslCipherUserKeySizeHeader() {
|
|
return sslCipherUserKeySizeHeader;
|
|
}
|
|
|
|
public void setSslCipherUserKeySizeHeader(String sslCipherUserKeySizeHeader) {
|
|
this.sslCipherUserKeySizeHeader = sslCipherUserKeySizeHeader;
|
|
}
|
|
|
|
|
|
public String mygetHeader(Request request, String header) {
|
|
String strcert0 = request.getHeader(header);
|
|
if (strcert0 == null) {
|
|
return null;
|
|
}
|
|
/* mod_header writes "(null)" when the ssl variable is no filled */
|
|
if ("(null)".equals(strcert0)) {
|
|
return null;
|
|
}
|
|
return strcert0;
|
|
}
|
|
|
|
|
|
@Override
|
|
public void invoke(Request request, Response response) throws IOException, ServletException {
|
|
/*
|
|
* Known behaviours of reverse proxies that are handled by the
|
|
* processing below:
|
|
* - mod_header converts the '\n' into ' '
|
|
* - nginx converts the '\n' into multiple ' '
|
|
*
|
|
* The code assumes that the trimmed header value starts with
|
|
* '-----BEGIN CERTIFICATE-----' and ends with
|
|
* '-----END CERTIFICATE-----'.
|
|
*
|
|
* Note: For Java 7, the the BEGIN and END markers must be on separate
|
|
* lines as must each of the original content lines. The
|
|
* CertificateFactory is tolerant of any additional whitespace
|
|
* such as leading and trailing spaces and new lines as long as
|
|
* they do not appear in the middle of an original content line.
|
|
*/
|
|
String headerValue = mygetHeader(request, sslClientCertHeader);
|
|
if (headerValue != null) {
|
|
headerValue = headerValue.trim();
|
|
if (headerValue.length() > 27) {
|
|
String body = headerValue.substring(27, headerValue .length() - 25);
|
|
body = body.replace(' ', '\n');
|
|
body = body.replace('\t', '\n');
|
|
String header = "-----BEGIN CERTIFICATE-----\n";
|
|
String footer = "\n-----END CERTIFICATE-----\n";
|
|
String strcerts = header.concat(body).concat(footer);
|
|
ByteArrayInputStream bais = new ByteArrayInputStream(
|
|
strcerts.getBytes(StandardCharsets.ISO_8859_1));
|
|
X509Certificate jsseCerts[] = null;
|
|
String providerName = (String) request.getConnector().getProperty(
|
|
"clientCertProvider");
|
|
try {
|
|
CertificateFactory cf;
|
|
if (providerName == null) {
|
|
cf = CertificateFactory.getInstance("X.509");
|
|
} else {
|
|
cf = CertificateFactory.getInstance("X.509", providerName);
|
|
}
|
|
X509Certificate cert = (X509Certificate) cf.generateCertificate(bais);
|
|
jsseCerts = new X509Certificate[1];
|
|
jsseCerts[0] = cert;
|
|
} catch (java.security.cert.CertificateException e) {
|
|
log.warn(sm.getString("sslValve.certError", strcerts), e);
|
|
} catch (NoSuchProviderException e) {
|
|
log.error(sm.getString(
|
|
"sslValve.invalidProvider", providerName), e);
|
|
}
|
|
request.setAttribute(Globals.CERTIFICATES_ATTR, jsseCerts);
|
|
}
|
|
}
|
|
headerValue = mygetHeader(request, sslCipherHeader);
|
|
if (headerValue != null) {
|
|
request.setAttribute(Globals.CIPHER_SUITE_ATTR, headerValue);
|
|
}
|
|
headerValue = mygetHeader(request, sslSessionIdHeader);
|
|
if (headerValue != null) {
|
|
request.setAttribute(Globals.SSL_SESSION_ID_ATTR, headerValue);
|
|
}
|
|
headerValue = mygetHeader(request, sslCipherUserKeySizeHeader);
|
|
if (headerValue != null) {
|
|
request.setAttribute(Globals.KEY_SIZE_ATTR, Integer.valueOf(headerValue));
|
|
}
|
|
getNext().invoke(request, response);
|
|
}
|
|
}
|