FELIX-4230 - Enhance SSL filter to forward client certificates:
- added some logging and exception handling to make sure that error
situations are traceable;
- did some monkey tests on SSL certificate forwarding, which seems to
work properly.
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1542257 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/HttpServiceTracker.java b/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/HttpServiceTracker.java
index 115da50..82f85f9 100644
--- a/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/HttpServiceTracker.java
+++ b/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/HttpServiceTracker.java
@@ -20,24 +20,28 @@
import java.util.HashMap;
import java.util.Hashtable;
+import java.util.Map;
import javax.servlet.ServletException;
import org.apache.felix.http.api.ExtHttpService;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
+import org.osgi.service.log.LogService;
import org.osgi.util.tracker.ServiceTracker;
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
public class HttpServiceTracker extends ServiceTracker
{
-
- private final HashMap /* ServiceReference, SslFilter */filters;
+ private final Map<ServiceReference, SslFilter> filters;
public HttpServiceTracker(BundleContext context)
{
super(context, ExtHttpService.class.getName(), null);
- this.filters = new HashMap();
+ this.filters = new HashMap<ServiceReference, SslFilter>();
}
public Object addingService(ServiceReference reference)
@@ -49,11 +53,14 @@
try
{
service.registerFilter(filter, ".*", new Hashtable(), 0, null);
+
this.filters.put(reference, filter);
+
+ SystemLogger.log(LogService.LOG_DEBUG, "SSL filter registered...");
}
catch (ServletException e)
{
- // TODO: log
+ SystemLogger.log(LogService.LOG_WARNING, "Failed to register SSL filter!", e);
}
}
@@ -66,6 +73,8 @@
if (filter != null)
{
((ExtHttpService) service).unregisterFilter(filter);
+
+ SystemLogger.log(LogService.LOG_DEBUG, "SSL filter unregistered...");
}
super.removedService(reference, service);
diff --git a/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/LogServiceTracker.java b/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/LogServiceTracker.java
new file mode 100644
index 0000000..87b8909
--- /dev/null
+++ b/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/LogServiceTracker.java
@@ -0,0 +1,50 @@
+/*
+ * 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.felix.http.sslfilter.internal;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.log.LogService;
+import org.osgi.util.tracker.ServiceTracker;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class LogServiceTracker extends ServiceTracker
+{
+ public LogServiceTracker(BundleContext context)
+ {
+ super(context, LogService.class.getName(), null);
+ }
+
+ @Override
+ public Object addingService(ServiceReference reference)
+ {
+ LogService result = (LogService) super.addingService(reference);
+ SystemLogger.setLogService(result);
+ return result;
+ }
+
+ @Override
+ public void removedService(ServiceReference reference, Object service)
+ {
+ SystemLogger.setLogService(null);
+ super.removedService(reference, service);
+ }
+}
diff --git a/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/SslFilter.java b/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/SslFilter.java
index 2807901..7460ea0 100644
--- a/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/SslFilter.java
+++ b/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/SslFilter.java
@@ -19,6 +19,8 @@
package org.apache.felix.http.sslfilter.internal;
import java.io.IOException;
+import java.security.cert.CertificateException;
+
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
@@ -27,9 +29,10 @@
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
+import org.osgi.service.log.LogService;
+
public class SslFilter implements Filter
{
-
// request header indicating an SSL endpoint proxy
private static final String X_FORWARD_SSL_HEADER = "X-Forwarded-SSL";
@@ -41,15 +44,23 @@
public void init(FilterConfig config)
{
+ // No explicit initialization needed...
}
- public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException,
- ServletException
+ public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException
{
HttpServletRequest httpReq = (HttpServletRequest) req;
if (X_FORWARD_SSL_VALUE.equalsIgnoreCase(httpReq.getHeader(X_FORWARD_SSL_HEADER)))
{
- httpReq = new SslFilterRequest(httpReq, httpReq.getHeader(X_FORWARD_SSL_CERTIFICATE_HEADER));
+ try
+ {
+ // In case this fails, we fall back to the original HTTP request, which is better than nothing...
+ httpReq = new SslFilterRequest(httpReq, httpReq.getHeader(X_FORWARD_SSL_CERTIFICATE_HEADER));
+ }
+ catch (CertificateException e)
+ {
+ SystemLogger.log(LogService.LOG_WARNING, "Failed to create SSL filter request! Problem parsing client certificates?! Client certificate will *not* be forwarded...", e);
+ }
}
// forward the request making sure any certificate is removed
@@ -69,6 +80,6 @@
public void destroy()
{
+ // No explicit destroy needed...
}
-
}
diff --git a/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/SslFilterActivator.java b/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/SslFilterActivator.java
index a52c4de..d4f36da 100644
--- a/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/SslFilterActivator.java
+++ b/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/SslFilterActivator.java
@@ -23,22 +23,29 @@
public class SslFilterActivator implements BundleActivator
{
-
- private HttpServiceTracker tracker;
+ private HttpServiceTracker httpTracker;
+ private LogServiceTracker logTracker;
public void start(BundleContext context)
{
- this.tracker = new HttpServiceTracker(context);
- this.tracker.open();
+ this.logTracker = new LogServiceTracker(context);
+ this.logTracker.open();
+
+ this.httpTracker = new HttpServiceTracker(context);
+ this.httpTracker.open();
}
public void stop(BundleContext context)
{
- if (this.tracker != null)
+ if (this.httpTracker != null)
{
- this.tracker.close();
- this.tracker = null;
+ this.httpTracker.close();
+ this.httpTracker = null;
+ }
+ if (this.logTracker != null)
+ {
+ this.logTracker.close();
+ this.logTracker = null;
}
}
-
}
diff --git a/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/SslFilterRequest.java b/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/SslFilterRequest.java
index 3c1c15d..63450af 100644
--- a/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/SslFilterRequest.java
+++ b/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/SslFilterRequest.java
@@ -32,7 +32,6 @@
class SslFilterRequest extends HttpServletRequestWrapper
{
-
// The HTTPS scheme name
private static final String HTTPS_SCHEME = "https";
@@ -56,41 +55,38 @@
* The first certificate in the chain is the one set by the client, the next
* is the one used to authenticate the first, and so on.
*/
- private static final String ATTR_SSL_CERTIFICATE = "javax.servlet.request.X509Certificate";
+ protected static final String ATTR_SSL_CERTIFICATE = "javax.servlet.request.X509Certificate";
private String requestURL;
- SslFilterRequest(final HttpServletRequest request, final String clientCertHeader)
+ @SuppressWarnings("unchecked")
+ SslFilterRequest(HttpServletRequest request, String clientCertHeader) throws CertificateException
{
super(request);
if (clientCertHeader != null && clientCertHeader.length() > 0)
{
-
final String clientCert = HEADER_TO_CERT.matcher(clientCertHeader).replaceAll("\n");
try
{
- InputStream instream = new ByteArrayInputStream(clientCert.getBytes(UTF_8));
CertificateFactory fac = CertificateFactory.getInstance("X.509");
- @SuppressWarnings("unchecked")
+
+ InputStream instream = new ByteArrayInputStream(clientCert.getBytes(UTF_8));
+
Collection<X509Certificate> certs = (Collection<X509Certificate>) fac.generateCertificates(instream);
request.setAttribute(ATTR_SSL_CERTIFICATE, certs.toArray(new X509Certificate[certs.size()]));
}
- catch (CertificateException e)
- {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
catch (UnsupportedEncodingException e)
{
- // TODO Auto-generated catch block
- e.printStackTrace();
+ // Any JRE should support UTF-8...
+ throw new InternalError("UTF-8 not supported?!");
}
}
}
- void done() {
+ void done()
+ {
getRequest().removeAttribute(ATTR_SSL_CERTIFICATE);
}
@@ -121,8 +117,4 @@
return new StringBuffer(this.requestURL);
}
-
- private final void provideCertificate(final HttpServletRequest request) throws UnsupportedEncodingException
- {
- }
}
diff --git a/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/SystemLogger.java b/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/SystemLogger.java
new file mode 100644
index 0000000..8193165
--- /dev/null
+++ b/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/SystemLogger.java
@@ -0,0 +1,82 @@
+/*
+ * 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.felix.http.sslfilter.internal;
+
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.log.LogService;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+final class SystemLogger
+{
+ private static volatile LogService log;
+
+ private SystemLogger()
+ {
+ // Nop
+ }
+
+ static void setLogService(LogService _log)
+ {
+ log = _log;
+ }
+
+ static LogService getLogService()
+ {
+ return log;
+ }
+
+ public static void log(int level, String message)
+ {
+ LogService service = getLogService();
+ if (service != null)
+ {
+ service.log(level, message);
+ }
+ }
+
+ public static void log(int level, String message, Throwable exception)
+ {
+ LogService service = getLogService();
+ if (service != null)
+ {
+ service.log(level, message, exception);
+ }
+ }
+
+ public static void log(ServiceReference sr, int level, String message)
+ {
+ LogService service = getLogService();
+ if (service != null)
+ {
+ service.log(sr, level, message);
+ }
+ }
+
+ public static void log(ServiceReference sr, int level, String message, Throwable exception)
+ {
+ LogService service = getLogService();
+ if (service != null)
+ {
+ service.log(sr, level, message, exception);
+ }
+ }
+}
diff --git a/http/sslfilter/src/test/java/org/apache/felix/http/sslfilter/internal/SslFilterRequestTest.java b/http/sslfilter/src/test/java/org/apache/felix/http/sslfilter/internal/SslFilterRequestTest.java
index 83ecfc0..c079a84 100644
--- a/http/sslfilter/src/test/java/org/apache/felix/http/sslfilter/internal/SslFilterRequestTest.java
+++ b/http/sslfilter/src/test/java/org/apache/felix/http/sslfilter/internal/SslFilterRequestTest.java
@@ -18,66 +18,67 @@
*/
package org.apache.felix.http.sslfilter.internal;
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import javax.servlet.http.HttpServletRequest;
-import junit.framework.TestCase;
-
import org.junit.Test;
import org.mockito.Mockito;
public class SslFilterRequestTest
{
-
@Test
- public void test_isSecure()
+ public void test_isSecure() throws Exception
{
- HttpServletRequest req = Mockito.mock(HttpServletRequest.class);
+ HttpServletRequest req = mock(HttpServletRequest.class);
SslFilterRequest sreq = new SslFilterRequest(req, null);
when(req.isSecure()).thenReturn(false);
- TestCase.assertFalse(req.isSecure());
- TestCase.assertTrue(sreq.isSecure());
- TestCase.assertFalse(req.isSecure());
+ assertFalse(req.isSecure());
+ assertTrue(sreq.isSecure());
+ assertFalse(req.isSecure());
when(req.isSecure()).thenReturn(true);
- TestCase.assertTrue(req.isSecure());
- TestCase.assertTrue(sreq.isSecure());
- TestCase.assertTrue(req.isSecure());
+ assertTrue(req.isSecure());
+ assertTrue(sreq.isSecure());
+ assertTrue(req.isSecure());
}
@Test
- public void test_getScheme()
+ public void test_getScheme() throws Exception
{
HttpServletRequest req = Mockito.mock(HttpServletRequest.class);
SslFilterRequest sreq = new SslFilterRequest(req, null);
when(req.getScheme()).thenReturn("http");
- TestCase.assertEquals("http", req.getScheme());
- TestCase.assertEquals("https", sreq.getScheme());
- TestCase.assertEquals("http", req.getScheme());
+ assertEquals("http", req.getScheme());
+ assertEquals("https", sreq.getScheme());
+ assertEquals("http", req.getScheme());
when(req.getScheme()).thenReturn("https");
- TestCase.assertEquals("https", req.getScheme());
- TestCase.assertEquals("https", sreq.getScheme());
- TestCase.assertEquals("https", req.getScheme());
+ assertEquals("https", req.getScheme());
+ assertEquals("https", sreq.getScheme());
+ assertEquals("https", req.getScheme());
}
@Test
- public void test_getRequestURL()
+ public void test_getRequestURL() throws Exception
{
HttpServletRequest req = Mockito.mock(HttpServletRequest.class);
SslFilterRequest sreq = new SslFilterRequest(req, null);
when(req.getRequestURL()).thenReturn(new StringBuffer("http://some/page"));
- TestCase.assertEquals("http://some/page", req.getRequestURL().toString());
- TestCase.assertEquals("https://some/page", sreq.getRequestURL().toString());
- TestCase.assertEquals("http://some/page", req.getRequestURL().toString());
+ assertEquals("http://some/page", req.getRequestURL().toString());
+ assertEquals("https://some/page", sreq.getRequestURL().toString());
+ assertEquals("http://some/page", req.getRequestURL().toString());
when(req.getRequestURL()).thenReturn(new StringBuffer("https://some/page"));
- TestCase.assertEquals("https://some/page", req.getRequestURL().toString());
- TestCase.assertEquals("https://some/page", sreq.getRequestURL().toString());
- TestCase.assertEquals("https://some/page", req.getRequestURL().toString());
+ assertEquals("https://some/page", req.getRequestURL().toString());
+ assertEquals("https://some/page", sreq.getRequestURL().toString());
+ assertEquals("https://some/page", req.getRequestURL().toString());
}
}