[java-identity-provider] branch master updated: IDP-1199 - Servlet filter for X509 flow behind proxy
Scott Cantor
cantor.2 at osu.edu
Mon Aug 20 17:01:31 EDT 2018
This is an automated email from the git hooks/post-receive script.
scantor pushed a commit to branch master
in repository java-identity-provider.
View the commit online:
http://git.shibboleth.net/view/?p=java-identity-provider.git;a=commit;h=81ef13f4d508a941641c5fc496e36756b949a02f
The following commit(s) were added to refs/heads/master by this push:
new 81ef13f IDP-1199 - Servlet filter for X509 flow behind proxy
81ef13f is described below
commit 81ef13f4d508a941641c5fc496e36756b949a02f
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Mon Aug 20 17:01:28 2018 -0400
IDP-1199 - Servlet filter for X509 flow behind proxy
https://issues.shibboleth.net/jira/browse/IDP-1199
---
.../shibboleth/idp/authn/impl/X509ProxyFilter.java | 140 ++++++++++++++++
.../idp/authn/impl/X509ProxyFilterTest.java | 177 +++++++++++++++++++++
2 files changed, 317 insertions(+)
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/X509ProxyFilter.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/X509ProxyFilter.java
new file mode 100644
index 0000000..31f374b
--- /dev/null
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/X509ProxyFilter.java
@@ -0,0 +1,140 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID 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 net.shibboleth.idp.authn.impl;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import javax.servlet.Filter;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import java.security.cert.X509Certificate;
+import org.opensaml.security.x509.X509Support;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Servlet filter to translate Apache mod_ssl certificate variables into Java servlet attributes.
+ */
+public class X509ProxyFilter implements Filter {
+
+ /** Init parameter controlling what headers to check for the leaf certificate. */
+ @Nonnull @NotEmpty public static final String LEAF_HEADER_PARAM = "leafHeader";
+
+ /** Init parameter controlling what headers to check for the chain certificates. */
+ @Nonnull @NotEmpty public static final String CHAIN_HEADERS_PARAM = "chainHeaders";
+
+ /** Apache null indicator. */
+ @Nonnull @NotEmpty private static final String APACHE_NULL = "(null)";
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(X509ProxyFilter.class);
+
+ /** Name of header containing end-entity certificate. */
+ @Nullable @NotEmpty private String leafHeader;
+
+ /** Name of headers containing chain certificates. */
+ @Nonnull @NonnullElements private Collection<String> chainHeaders;
+
+ /** Constructor. */
+ public X509ProxyFilter() {
+ chainHeaders = Collections.emptyList();
+ }
+
+ /** {@inheritDoc} */
+ public void init(final FilterConfig config) throws ServletException {
+ leafHeader = config.getInitParameter(LEAF_HEADER_PARAM);
+ if (leafHeader == null) {
+ throw new ServletException("Required init-parameter " + LEAF_HEADER_PARAM + " missing");
+ }
+ log.info("X509ProxyFilter will check for the end-entity certificate in: {}", leafHeader);
+
+ final String param = config.getInitParameter(CHAIN_HEADERS_PARAM);
+ if (param != null) {
+ final String[] headers = param.split(" ");
+ if (headers != null) {
+ chainHeaders = StringSupport.normalizeStringCollection(Arrays.asList(headers));
+ }
+ }
+ log.info("X509ProxyFilter will check for chain certificates in: {}", chainHeaders);
+ }
+
+// Checkstyle: CyclomaticComplexity OFF
+ /** {@inheritDoc} */
+ public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain)
+ throws IOException, ServletException {
+ final HttpServletRequest httpRequest = (HttpServletRequest) request;
+
+ try {
+ final X509Certificate[] certs =
+ (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate");
+
+ if (null == certs || 0 == certs.length) {
+ final List<X509Certificate> proxyCerts = new ArrayList<X509Certificate>();
+
+ if (leafHeader != null) {
+ final String pem = httpRequest.getHeader(leafHeader);
+ if (pem != null && !pem.isEmpty() && !APACHE_NULL.equals(pem)) {
+ proxyCerts.add(X509Support.decodeCertificate(pem.getBytes()));
+ } else {
+ log.warn("No end-entity certificate found");
+ return;
+ }
+ } else {
+ log.warn("No end-entity certificate found");
+ return;
+ }
+
+ for (final String s : chainHeaders) {
+ final String pem = httpRequest.getHeader(s);
+ if (pem != null && !pem.isEmpty() && !APACHE_NULL.equals(pem)) {
+ proxyCerts.add(X509Support.decodeCertificate(pem.getBytes()));
+ }
+ }
+
+ if (!proxyCerts.isEmpty()) {
+ request.setAttribute("javax.servlet.request.X509Certificate",
+ proxyCerts.toArray(new X509Certificate[proxyCerts.size()]));
+ }
+ }
+ } catch (final Exception e) {
+ log.error(e.getMessage());
+ } finally {
+ chain.doFilter(request, response);
+ }
+ }
+// Checkstyle: CyclomaticComplexity ON
+
+ /** {@inheritDoc} */
+ public void destroy() {
+
+ }
+
+}
\ No newline at end of file
diff --git a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/X509ProxyFilterTest.java b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/X509ProxyFilterTest.java
new file mode 100644
index 0000000..fa7eb53
--- /dev/null
+++ b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/X509ProxyFilterTest.java
@@ -0,0 +1,177 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID 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 net.shibboleth.idp.authn.impl;
+
+
+import java.io.IOException;
+import java.security.cert.X509Certificate;
+
+import javax.servlet.ServletException;
+import javax.servlet.ServletResponse;
+
+import org.springframework.mock.web.MockFilterChain;
+import org.springframework.mock.web.MockFilterConfig;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+/** {@link X509ProxyFilter} unit test. */
+public class X509ProxyFilterTest extends BaseAuthenticationContextTest {
+
+ private String entityCertBase64 =
+ "-----BEGIN CERTIFICATE-----\n" +
+ "MIIDjDCCAnSgAwIBAgIBKjANBgkqhkiG9w0BAQUFADAtMRIwEAYDVQQKEwlJbnRl" +
+ "cm5ldDIxFzAVBgNVBAMTDmNhLmV4YW1wbGUub3JnMB4XDTA3MDQwOTA2MTIwOVoX" +
+ "DTE3MDQwNjA2MTIwOVowMTESMBAGA1UEChMJSW50ZXJuZXQyMRswGQYDVQQDExJm" +
+ "b29iYXIuZXhhbXBsZS5vcmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB" +
+ "AQDNWnkFmhy1vYa6gN/xBRKkZxFy3sUq2V0LsYb6Q3pe9Qlb6+BzaM5DrN8uIqqr" +
+ "oBE3Wp0LtrgKuQTpDpNFBdS2p5afiUtOYLWBDtizTOzs3Z36MGMjIPUYQ4s03IP3" +
+ "yPh2ud6EKpDPiYqzNbkRaiIwmYSit5r+RMYvd6fuKvTOn6h7PZI5AD7Rda7VWh5O" +
+ "VSoZXlRx3qxFho+mZhW0q4fUfTi5lWwf4EhkfBlzgw/k5gf4cOi6rrGpRS1zxmbt" +
+ "X1RAg+I20z6d04g0N2WsK5stszgYKoIROJCiXwjraa8/SoFcILolWQpttVHBIUYl" +
+ "yDlm8mIFleZf4ReFpfm+nUYxAgMBAAGjgbIwga8wCQYDVR0TBAIwADAsBglghkgB" +
+ "hvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYE" +
+ "FDgRgTkjaKoK6DoZfUZ4g9LDJUWuMFUGA1UdIwROMEyAFNXuZVPeUdqHrULqQW7y" +
+ "r9buRpQLoTGkLzAtMRIwEAYDVQQKEwlJbnRlcm5ldDIxFzAVBgNVBAMTDmNhLmV4" +
+ "YW1wbGUub3JnggEBMA0GCSqGSIb3DQEBBQUAA4IBAQCPj3Si4Eiw9abNgPBUhBXW" +
+ "d6eRYlIHaHcnez6j6g7foAOyuVIUso9Q5c6pvL87lmasK55l09YPXw1qmiH+bHMc" +
+ "rwEPODpLx7xd3snlOCi7FyxahxwSs8yfTu8Pq95rWt0LNcfHxQK938Cpnav6jgDo" +
+ "2uH/ywAOFFSnoBzGHAfScHMfj8asZ6THosYsklII7FSU8j49GV2utkvGB3mcu4ST" +
+ "uLdeRCZmi93vq1D4JVGsXC4UaHjg114+a+9q0XZdz6a1UW4pt1ryXIPotCS62M71" +
+ "pkJf5neHUinKAqgoRfPXowudZg1Zl8DjzoOBn+MNHRrR5KYbVGvdHcxoJLCwVB/v" +
+ "\n-----END CERTIFICATE-----\n";
+
+ private String otherCert1Base64 =
+ "-----BEGIN CERTIFICATE-----\n" +
+ "MIIECTCCAvGgAwIBAgIBMzANBgkqhkiG9w0BAQUFADAtMRIwEAYDVQQKEwlJbnRl" +
+ "cm5ldDIxFzAVBgNVBAMTDmNhLmV4YW1wbGUub3JnMB4XDTA3MDUyNTIwMTYxMVoX" +
+ "DTE3MDUyMjIwMTYxMVowGjEYMBYGA1UEAxMPaWRwLmV4YW1wbGUub3JnMIIBtjCC" +
+ "ASsGByqGSM44BAEwggEeAoGBAI+ktw7R9m7TxjaCrT2MHwWNQUAyXPrqbFCcu+DC" +
+ "irr861U6R6W/GyqWdcy8/D1Hh/I1U94POQn5yfqVPpVH2ZRS4OMFndHWaoo9V5LJ" +
+ "oXTXHiDYB3W4t9tn0fm7It0n7VoUI5C4y9LG32Hq+UIGF/ktNTmo//mEqLS6aJNd" +
+ "bMFpAhUArmKGh0hcpmjukYArWcMRvipB4CMCgYBuCiCrUaHBRRtqrk0P/Luq0l2M" +
+ "2718GwSGeLPZip06gACDG7IctMrgH1J+ZIjsx6vffi977wnMDiktqacmaobV+SCR" +
+ "W9ijJRdkYpUHmlLvuJGnDPjkvewpbGWJsCabpWEvWdYw3ma8RuHOPj4Jkrdd4VcR" +
+ "aFwox/fPJ7cG6kBydgOBhAACgYBxQIPv9DCsmiMHG1FAxSARX0GcRiELJPJ+MtaS" +
+ "tdTrVobNa2jebwc3npLiTvUR4U/CDo1mSZb+Sp/wian8kNZHmGcR6KbtJs9UDsa3" +
+ "V0pbbgpUar4HcxV+NQJBbhn9RGu85g3PDILUrINiUAf26mhPN5Y0paM+HbM68nUf" +
+ "1OLv16OBsjCBrzAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdl" +
+ "bmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUIHFAEB/3jIIZzJEJ/qdsuI8v" +
+ "N3kwVQYDVR0jBE4wTIAU1e5lU95R2oetQupBbvKv1u5GlAuhMaQvMC0xEjAQBgNV" +
+ "BAoTCUludGVybmV0MjEXMBUGA1UEAxMOY2EuZXhhbXBsZS5vcmeCAQEwDQYJKoZI" +
+ "hvcNAQEFBQADggEBAJt4Q34+pqjW5tHHhkdzTITSBjOOf8EvYMgxTMRzhagLSHTt" +
+ "9RgO5i/G7ELvnwe1j6187m1XD9iEAWKeKbB//ljeOpgnwzkLR9Er5tr1RI3cbil0" +
+ "AX+oX0c1jfRaQnR50Rfb5YoNX6G963iphlxp9C8VLB6eOk/S270XoWoQIkO1ioQ8" +
+ "JY4HE6AyDsOpJaOmHpBaxjgsiko52ZWZeZyaCyL98BXwVxeml7pYnHlXWWidB0N/" +
+ "Zy+LbvWg3urUkiDjMcB6nGImmEfDSxRdybitcMwbwL26z2WOpwL3llm3mcCydKXg" +
+ "Xt8IQhfDhOZOHWckeD2tStnJRP/cqBgO62/qirw=" +
+ "\n-----END CERTIFICATE-----\n";
+
+ private X509ProxyFilter filter;
+
+ private MockHttpServletRequest request;
+
+ @BeforeMethod public void setUp() throws Exception {
+ super.setUp();
+
+ final MockFilterConfig config = new MockFilterConfig();
+ config.addInitParameter(X509ProxyFilter.LEAF_HEADER_PARAM, "SSL_CLIENT_CERT");
+ config.addInitParameter(X509ProxyFilter.CHAIN_HEADERS_PARAM,
+ "SSL_CLIENT_CERT_CHAIN_0 SSL_CLIENT_CERT_CHAIN_1 SSL_CLIENT_CERT_CHAIN_2 SSL_CLIENT_CERT_CHAIN_3");
+
+ filter = new X509ProxyFilter();
+ filter.init(config);
+
+ request = (MockHttpServletRequest) src.getExternalContext().getNativeRequest();
+ }
+
+ @Test(expectedExceptions=ServletException.class) public void testBadConfig() throws ServletException {
+
+ final MockFilterConfig config = new MockFilterConfig();
+
+ filter = new X509ProxyFilter();
+ filter.init(config);
+ }
+
+ @Test public void testNoCertificates() throws IOException, ServletException {
+
+ filter.doFilter(
+ request,
+ (ServletResponse) src.getExternalContext().getNativeResponse(),
+ new MockFilterChain());
+
+ Assert.assertNull(request.getAttribute("javax.servlet.request.X509Certificate"));
+ }
+
+ @Test public void testPreexistingCertificates() throws IOException, ServletException {
+
+ request.setAttribute("javax.servlet.request.X509Certificate", "foo");
+
+ filter.doFilter(
+ request,
+ (ServletResponse) src.getExternalContext().getNativeResponse(),
+ new MockFilterChain());
+
+ Assert.assertEquals("foo" ,request.getAttribute("javax.servlet.request.X509Certificate"));
+ }
+
+
+ @Test public void testNoEntityCertificate() throws IOException, ServletException {
+
+ request.addHeader("SSL_CLIENT_CERT_CHAIN_0", otherCert1Base64);
+
+ filter.doFilter(
+ request,
+ (ServletResponse) src.getExternalContext().getNativeResponse(),
+ new MockFilterChain());
+
+ Assert.assertNull(request.getAttribute("javax.servlet.request.X509Certificate"));
+ }
+
+ @Test public void testEntityCertificate() throws IOException, ServletException {
+
+ request.addHeader("SSL_CLIENT_CERT", entityCertBase64);
+
+ filter.doFilter(
+ request,
+ (ServletResponse) src.getExternalContext().getNativeResponse(),
+ new MockFilterChain());
+
+ Assert.assertEquals(1, ((X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate")).length);
+ Assert.assertEquals("CN=foobar.example.org, O=Internet2",
+ ((X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate"))[0].getSubjectDN().toString());
+ }
+
+ @Test public void testCertificateChain() throws IOException, ServletException {
+
+ request.addHeader("SSL_CLIENT_CERT", entityCertBase64);
+ request.addHeader("SSL_CLIENT_CERT_CHAIN_0", otherCert1Base64);
+
+ filter.doFilter(
+ request,
+ (ServletResponse) src.getExternalContext().getNativeResponse(),
+ new MockFilterChain());
+
+ Assert.assertEquals(2, ((X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate")).length);
+ Assert.assertEquals("CN=foobar.example.org, O=Internet2",
+ ((X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate"))[0].getSubjectDN().toString());
+ Assert.assertEquals("CN=idp.example.org",
+ ((X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate"))[1].getSubjectDN().toString());
+ }
+}
\ No newline at end of file
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list