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());
     }
 }