/*
* Copyright 2012 Research Studios Austria Forschungsges.m.b.H.
*
* 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 won.cryptography.webid.springsecurity;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
import org.springframework.security.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor;
import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor;
import won.protocol.vocabulary.WONCRYPT;
import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayInputStream;
import java.io.UnsupportedEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
/**
* Created by fkleedorfer on 28.11.2016.
*/
public class ReverseProxyCompatibleX509AuthenticationFilter extends AbstractPreAuthenticatedProcessingFilter
{
private final boolean behindProxy;
private X509PrincipalExtractor principalExtractor = new SubjectDnX509PrincipalExtractor();
public ReverseProxyCompatibleX509AuthenticationFilter(final boolean behindProxy) {
this.behindProxy = behindProxy;
}
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
X509Certificate cert = extractClientCertificate(request);
if (cert == null) {
return null;
}
return principalExtractor.extractPrincipal(cert);
}
@Override
protected Object getPreAuthenticatedCredentials(final HttpServletRequest request) {
return extractClientCertificate(request);
}
/**
* Depending on the value of behindProxy, the certificate is extracted from the request context or from the
* 'X-Client-Certificate' header.
* @param request
* @return
*/
private X509Certificate extractClientCertificate(HttpServletRequest request) {
X509Certificate[] certificateChainObj = null;
if (behindProxy) {
CertificateFactory certificateFactory = null;
try {
certificateFactory = CertificateFactory.getInstance("X.509");
} catch (CertificateException e) {
throw new InternalAuthenticationServiceException("could not extract certificate from request", e);
}
String certificateHeader = request.getHeader(WONCRYPT.CLIENT_CERTIFICATE_HEADER);
if (certificateHeader == null) {
throw new AuthenticationCredentialsNotFoundException(
"No HTTP header 'X-Client-Certificate' set that contains client authentication certificate! If property " +
"'client.authentication.behind.proxy' is set to true, this header must be " +
"set by the reverse proxy!");
}
// the load balancer (e.g. nginx) forwards the certificate into a header by replacing new lines with whitespaces
// (2 or more for nginx, 1 for apache 2.4 - for the latter case we have to add the lookbehind pattern). Also replace tabs, which sometimes nginx may send instead of whitespace
String certificateContent = certificateHeader.replaceAll("(?<!-----BEGIN|-----END)\\s+", System.lineSeparator())
.replaceAll("\\t+", System.lineSeparator());
if (logger.isDebugEnabled()){
logger.debug("found this certificate in the " + WONCRYPT.CLIENT_CERTIFICATE_HEADER + " header: "+ certificateHeader);
logger.debug("found this certificate in the " + WONCRYPT.CLIENT_CERTIFICATE_HEADER + " header (after whitespace replacement): " + certificateContent);
}
X509Certificate[] userCertificate = new X509Certificate[1];
try {
userCertificate[0] = (X509Certificate) certificateFactory
.generateCertificate(new ByteArrayInputStream(certificateContent.getBytes("ISO-8859-11")));
} catch (CertificateException e) {
throw new AuthenticationCredentialsNotFoundException("could not extract certificate from request", e);
} catch (UnsupportedEncodingException e) {
throw new AuthenticationCredentialsNotFoundException("could not extract certificate from request with encoding " +
"ISO-8859-11",e);
}
certificateChainObj = userCertificate;
} else {
certificateChainObj = (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate");
if (certificateChainObj == null) {
throw new AuthenticationCredentialsNotFoundException(
"Client certificate attribute is null! Check if you are behind a proxy server that takes care about the " +
"client authentication already. If so, set the property 'client.authentication.behind.proxy' to true and " +
"make sure the proxy sets the HTTP header 'X-Client-Certificate' appropriately to the sent client certificate");
}
}
return certificateChainObj[0];
}
public void setPrincipalExtractor(X509PrincipalExtractor principalExtractor) {
this.principalExtractor = principalExtractor;
}
}