/* * Copyright (c) 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017 David Berkman * * This file is part of the SmallMind Code Project. * * The SmallMind Code Project is free software, you can redistribute * it and/or modify it under either, at your discretion... * * 1) The terms of GNU Affero General Public License as published by the * Free Software Foundation, either version 3 of the License, or (at * your option) any later version. * * ...or... * * 2) The terms of the Apache License, Version 2.0. * * The SmallMind Code Project is distributed in the hope that it will * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License or Apache License for more details. * * You should have received a copy of the GNU Affero General Public License * and the Apache License along with the SmallMind Code Project. If not, see * <http://www.gnu.org/licenses/> or <http://www.apache.org/licenses/LICENSE-2.0>. * * Additional permission under the GNU Affero GPL version 3 section 7 * ------------------------------------------------------------------ * If you modify this Program, or any covered work, by linking or * combining it with other code, such other code is not for that reason * alone subject to any of the requirements of the GNU Affero GPL * version 3. */ package org.smallmind.web.grizzly; import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.InetSocketAddress; import java.security.cert.Certificate; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import javax.net.ssl.SSLEngine; import org.glassfish.grizzly.filterchain.BaseFilter; import org.glassfish.grizzly.filterchain.FilterChainContext; import org.glassfish.grizzly.filterchain.NextAction; import org.glassfish.grizzly.http.HttpContent; import org.glassfish.grizzly.http.HttpHeader; import org.glassfish.grizzly.http.HttpPacket; import org.glassfish.grizzly.http.util.HeaderValue; import org.glassfish.grizzly.ssl.SSLUtils; public class ClientAuthProxyFilter extends BaseFilter { protected boolean proxyMode; //Subject DN static public final String SSL_CLIENT_S_DN = "SSL_CLIENT_S_DN"; //Issuer DN static public final String SSL_CLIENT_I_DN = "SSL_CLIENT_I_DN"; //Indicate the certificate was already checked (NONE, SUCCESS, GENEROUS or FAILED) static public final String SSL_CLIENT_VERIFY = "SSL_CLIENT_VERIFY"; //Field where proxy indicates IP of original client static public final String X_FORWARDED_FOR = "X-Forwarded-For"; public ClientAuthProxyFilter (boolean proxyMode) { this.proxyMode = proxyMode; } public NextAction handleRead (FilterChainContext ctx) throws IOException { if (HttpPacket.isHttp(ctx.getMessage())) { HttpContent httpContent = ctx.getMessage(); /** * Clear out SSL headers to prevent identity spoofing. If proxy mode is on * then a proxy MUST be the only way to access this machine otherwise someone * can claim auth by modifying headers */ if (this.proxyMode) { return ctx.getInvokeAction(); } if (httpContent.getHttpHeader().containsHeader(SSL_CLIENT_S_DN)) { httpContent.getHttpHeader().setHeader(SSL_CLIENT_S_DN, (HeaderValue)null); } if (httpContent.getHttpHeader().containsHeader(SSL_CLIENT_I_DN)) { httpContent.getHttpHeader().setHeader(SSL_CLIENT_I_DN, (HeaderValue)null); } if (httpContent.getHttpHeader().containsHeader(SSL_CLIENT_VERIFY)) { httpContent.getHttpHeader().setHeader(SSL_CLIENT_VERIFY, (HeaderValue)null); } if (httpContent.getHttpHeader().containsHeader(X_FORWARDED_FOR)) { httpContent.getHttpHeader().setHeader(X_FORWARDED_FOR, (HeaderValue)null); } /** * Set client IP */ this.extractIPAddress((InetSocketAddress)ctx.getConnection().getPeerAddress(), httpContent.getHttpHeader()); /** * Check for SSL headers */ SSLEngine sslEngine = SSLUtils.getSSLEngine(ctx.getConnection()); if (sslEngine == null) { return ctx.getInvokeAction(); } //Extract certificates Certificate[] certs = this.getPeerCertificates(SSLUtils.getSSLEngine(ctx.getConnection())); X509Certificate[] x509Certs = this.extractX509Certs(certs); if (x509Certs == null || x509Certs.length == 0 || x509Certs[0].getSubjectDN() == null) { return ctx.getInvokeAction(); } //set headers. first cert in chain is always peer cert httpContent.getHttpHeader().addHeader(SSL_CLIENT_S_DN, x509Certs[0].getSubjectDN().getName()); if (x509Certs[0].getIssuerDN() != null) { httpContent.getHttpHeader().addHeader(SSL_CLIENT_I_DN, x509Certs[0].getIssuerDN().getName()); } httpContent.getHttpHeader().addHeader(SSL_CLIENT_VERIFY, "SUCCESS"); } return ctx.getInvokeAction(); } private X509Certificate[] extractX509Certs (final Certificate[] certs) { if (certs == null) { return null; } final X509Certificate[] x509Certs = new X509Certificate[certs.length]; for (int i = 0, len = certs.length; i < len; i++) { if (certs[i] instanceof X509Certificate) { x509Certs[i] = (X509Certificate)certs[i]; } else { try { final byte[] buffer = certs[i].getEncoded(); final CertificateFactory cf = CertificateFactory.getInstance("X.509"); ByteArrayInputStream stream = new ByteArrayInputStream(buffer); x509Certs[i] = (X509Certificate)cf.generateCertificate(stream); } catch (Exception ex) { return null; } } } return x509Certs; } private Certificate[] getPeerCertificates (final SSLEngine sslEngine) { try { return sslEngine.getSession().getPeerCertificates(); } catch (Throwable t) { return null; } } private void extractIPAddress (InetSocketAddress peerAddress, HttpHeader httpHeader) { if (peerAddress == null || peerAddress.getAddress() == null) { return; } httpHeader.setHeader(X_FORWARDED_FOR, peerAddress.getAddress().getHostAddress()); } }