| /* |
| * 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.catalina.valves; |
| |
| import java.security.cert.X509Certificate; |
| import java.util.Arrays; |
| import java.util.logging.Level; |
| |
| import org.junit.Assert; |
| import org.junit.Before; |
| import org.junit.Test; |
| |
| import org.apache.catalina.Globals; |
| import org.apache.catalina.Valve; |
| import org.apache.catalina.connector.Connector; |
| import org.apache.catalina.connector.Request; |
| import org.apache.tomcat.unittest.TesterLogValidationFilter; |
| import org.easymock.EasyMock; |
| |
| public class TestSSLValve { |
| |
| public static class MockRequest extends Request { |
| |
| public MockRequest() { |
| setConnector(EasyMock.createMock(Connector.class)); |
| setCoyoteRequest(new org.apache.coyote.Request()); |
| } |
| |
| @Override |
| public void setAttribute(String name, Object value) { |
| getCoyoteRequest().getAttributes().put(name, value); |
| } |
| |
| @Override |
| public Object getAttribute(String name) { |
| return getCoyoteRequest().getAttribute(name); |
| } |
| |
| public void setHeader(String header, String value) { |
| getCoyoteRequest().getMimeHeaders().setValue(header).setString(value); |
| } |
| |
| public void addHeader(String header, String value) { |
| getCoyoteRequest().getMimeHeaders().addValue(header).setString(value); |
| } |
| } |
| |
| private static final String[] CERTIFICATE_LINES = new String[] { "-----BEGIN CERTIFICATE-----", |
| "MIIFXTCCA0WgAwIBAgIJANFf3YTJgYifMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV", |
| "BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX", |
| "aWRnaXRzIFB0eSBMdGQwHhcNMTcwNTI2MjEzNjM3WhcNMTgwNTI2MjEzNjM3WjBF", |
| "MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50", |
| "ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC", |
| "CgKCAgEA2ykNBanZz4cVITNpZcWNVmErUzqgSNrK361mj9vEdB1UkHatwal9jVrR", |
| "QvFgfiZ8Gl+/85t0ebJhJ+rIr1ww6JE7v2s2MThENj95K5EwZOmgvw+CBlBYsFIz", |
| "8BtjlVYy+v7RaGPXfjrFkexQP9UIaiIIog2ClDZirRvb+QxS930/YW5Qo+X6EX6W", |
| "/m/HvlorD25U4ni2FQ0y+EMO2e1jD88cAAMoP5f+Mf6NBK8I6yUeaSuMq7WqtHGV", |
| "e4F1WOg5z9J5c/M69rB0iQr5NUQwZ1mPYf5Kr0P6+TLh8DJphbVvmHJyT3bgofeV", |
| "JYl/kdjiXS5P/jwY9tfmhu04tsyzopWRUFCcj5zCiqZYaMn0wtDn08KaAh9oOlg8", |
| "Z6mJ9i5EybkLm63W7z7LxuM+qnYzq4wKkKdx8hbpASwPqzJkJeXFL/LzhKdZuHiR", |
| "clgPVYnm98URwhObh073dKguG/gkhcnpXcVBBVdVTJZYGBvTpQh0afXd9bcBwOzY", |
| "t4MDpGiQB2fLzBOEZhQ37kUcWPmZw5bNPxhx4yE96Md0rx/Gu4ipAHuqLemb1SL5", |
| "uWNesVmgY3OXaIamQIm9BCwkf8mMvoYdAT+lukTUZLtJ6s2w+Oxnl10tmb+6sTXy", |
| "UB3WcBTp/o3YjAyJPnM1Wq6nVNQ4W2+NbV5purGAP09sumxeJj8CAwEAAaNQME4w", |
| "HQYDVR0OBBYEFCGOYMvymUG2ZZT+lK4LvwEvx731MB8GA1UdIwQYMBaAFCGOYMvy", |
| "mUG2ZZT+lK4LvwEvx731MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIB", |
| "AG6m4nDYCompUtRVude1qulwwAaYEMHyIIsfymI8uAE7d2o4bGjVpAUOdH/VWSOp", |
| "Rzx0oK6K9cHyiBlKHw5zSZdqcRi++tDX3P9Iy5tXO//zkhMEnSpk6RF2+9JXtyhx", |
| "Gma4yAET1yES+ybiFT21uZrGCC9r69rWG8JRZshc4RVWGwZsd0zrATVqfY0mZurm", |
| "xLgU4UOvkTczjlrLiklwwU68M1DLcILJ5FZGTWeTJ/q1wpIn9isK2siAW/VOcbuG", |
| "xdbGladnIFv+iQfuZG0yjcuMsBFsQiXi6ONM8GM+dr+61V63/1s73jYcOToEsTMM", |
| "3bHeVffoSkhZvOGTRCI6QhK9wqnIKhAYqu+NbV4OphfE3gOaK+T1cASXUtSQPXoa", |
| "sEoIVmbQsWRBhWvYShVqvINsH/hAT3Cf/+SslprtQUqiyt2ljdgrRFZdoyB3S7ky", |
| "KWoZRvHRj2cKU65LVYwx6U1A8SGmViz4aHMSai0wwKzOVv9MGHeRaVhlMmMsbdfu", |
| "wKoKJv0xYoVwEh1rB8TH8PjbL+6eFLeZXYVZnH71d5JHCghZ8W0a11ZuYkscSQWk", |
| "yoTBqEpJloWksrypqp3iL4PAL5+KkB2zp66+MVAg8LcEDFJggBBJCtv4SCWV7ZOB", |
| "WLu8gep+XCwSn0Wb6D3eFs4DoIiMvQ6g2rS/pk7o5eWj", "-----END CERTIFICATE-----" }; |
| |
| private SSLValve valve = new SSLValve(); |
| |
| private MockRequest mockRequest = new MockRequest(); |
| private Valve mockNext = EasyMock.createMock(Valve.class); |
| |
| |
| @Before |
| public void setUp() throws Exception { |
| valve.setNext(mockNext); |
| mockNext.invoke(mockRequest, null); |
| EasyMock.replay(mockNext); |
| } |
| |
| |
| @Test |
| public void testSslHeader() { |
| final String headerName = "myheader"; |
| final String headerValue = "BASE64_HEADER_VALUE"; |
| mockRequest.setHeader(headerName, headerValue); |
| |
| Assert.assertEquals(headerValue, valve.mygetHeader(mockRequest, headerName)); |
| } |
| |
| |
| @Test |
| public void testSslHeaderNull() { |
| final String headerName = "myheader"; |
| mockRequest.setHeader(headerName, null); |
| |
| Assert.assertNull(valve.mygetHeader(mockRequest, headerName)); |
| } |
| |
| |
| @Test |
| public void testSslHeaderNullModHeader() { |
| final String headerName = "myheader"; |
| final String nullModHeaderValue = "(null)"; |
| mockRequest.setHeader(headerName, nullModHeaderValue); |
| |
| Assert.assertNull(valve.mygetHeader(mockRequest, nullModHeaderValue)); |
| } |
| |
| |
| @Test |
| public void testSslHeaderNullName() throws Exception { |
| Assert.assertNull(valve.mygetHeader(mockRequest, null)); |
| } |
| |
| |
| @Test |
| public void testSslHeaderMultiples() throws Exception { |
| final String headerName = "myheader"; |
| final String headerValue = "BASE64_HEADER_VALUE"; |
| mockRequest.addHeader(headerName, headerValue); |
| mockRequest.addHeader(headerName, "anyway won't be found"); |
| |
| Assert.assertEquals(headerValue, valve.mygetHeader(mockRequest, headerName)); |
| } |
| |
| |
| @Test |
| public void testSslClientCertHeaderSingleSpace() throws Exception { |
| String singleSpaced = certificateSingleLine(" "); |
| mockRequest.setHeader(valve.getSslClientCertHeader(), singleSpaced); |
| |
| valve.invoke(mockRequest, null); |
| |
| assertCertificateParsed(); |
| } |
| |
| |
| @Test |
| public void testSslClientCertHeaderMultiSpace() throws Exception { |
| String singleSpaced = certificateSingleLine(" "); |
| mockRequest.setHeader(valve.getSslClientCertHeader(), singleSpaced); |
| |
| valve.invoke(mockRequest, null); |
| |
| assertCertificateParsed(); |
| } |
| |
| |
| @Test |
| public void testSslClientCertHeaderTab() throws Exception { |
| String singleSpaced = certificateSingleLine("\t"); |
| mockRequest.setHeader(valve.getSslClientCertHeader(), singleSpaced); |
| |
| valve.invoke(mockRequest, null); |
| |
| assertCertificateParsed(); |
| } |
| |
| |
| @Test |
| public void testSslClientCertNull() throws Exception { |
| TesterLogValidationFilter f = TesterLogValidationFilter.add(null, "", null, |
| "org.apache.catalina.valves.SSLValve"); |
| |
| valve.invoke(mockRequest, null); |
| |
| EasyMock.verify(mockNext); |
| Assert.assertNull(mockRequest.getAttribute(Globals.CERTIFICATES_ATTR)); |
| Assert.assertEquals(0, f.getMessageCount()); |
| } |
| |
| |
| @Test |
| public void testSslClientCertShorter() throws Exception { |
| mockRequest.setHeader(valve.getSslClientCertHeader(), "shorter than hell"); |
| |
| TesterLogValidationFilter f = TesterLogValidationFilter.add(null, "", null, |
| "org.apache.catalina.valves.SSLValve"); |
| |
| valve.invoke(mockRequest, null); |
| |
| EasyMock.verify(mockNext); |
| Assert.assertNull(mockRequest.getAttribute(Globals.CERTIFICATES_ATTR)); |
| Assert.assertEquals(0, f.getMessageCount()); |
| } |
| |
| |
| @Test |
| public void testSslClientCertIgnoredBegin() throws Exception { |
| String[] linesBegin = Arrays.copyOf(CERTIFICATE_LINES, CERTIFICATE_LINES.length); |
| linesBegin[0] = "3fisjcme3kdsakasdfsadkafsd3"; |
| String begin = certificateSingleLine(linesBegin, " "); |
| mockRequest.setHeader(valve.getSslClientCertHeader(), begin); |
| |
| valve.invoke(mockRequest, null); |
| |
| assertCertificateParsed(); |
| } |
| |
| |
| @Test |
| public void testSslClientCertBadFormat() throws Exception { |
| String[] linesDeleted = Arrays.copyOf(CERTIFICATE_LINES, CERTIFICATE_LINES.length / 2); |
| String deleted = certificateSingleLine(linesDeleted, " "); |
| mockRequest.setHeader(valve.getSslClientCertHeader(), deleted); |
| |
| TesterLogValidationFilter f = TesterLogValidationFilter.add(Level.WARNING, null, |
| "java.security.cert.CertificateException", "org.apache.catalina.valves.SSLValve"); |
| |
| valve.invoke(mockRequest, null); |
| |
| EasyMock.verify(mockNext); |
| Assert.assertNull(mockRequest.getAttribute(Globals.CERTIFICATES_ATTR)); |
| Assert.assertEquals(1, f.getMessageCount()); |
| } |
| |
| |
| @Test |
| public void testClientCertProviderNotFound() throws Exception { |
| EasyMock.expect(mockRequest.getConnector().getProperty("clientCertProvider")).andStubReturn("wontBeFound"); |
| EasyMock.replay(mockRequest.getConnector()); |
| mockRequest.setHeader(valve.getSslClientCertHeader(), certificateSingleLine(" ")); |
| |
| TesterLogValidationFilter f = TesterLogValidationFilter.add(Level.SEVERE, null, |
| "java.security.NoSuchProviderException", "org.apache.catalina.valves.SSLValve"); |
| |
| valve.invoke(mockRequest, null); |
| |
| Assert.assertNull(mockRequest.getAttribute(Globals.CERTIFICATES_ATTR)); |
| Assert.assertEquals(1, f.getMessageCount()); |
| } |
| |
| |
| @Test |
| public void testSslCipherHeaderPresent() throws Exception { |
| String cipher = "ciphered-with"; |
| mockRequest.setHeader(valve.getSslCipherHeader(), cipher); |
| |
| valve.invoke(mockRequest, null); |
| |
| Assert.assertEquals(cipher, mockRequest.getAttribute(Globals.CIPHER_SUITE_ATTR)); |
| } |
| |
| |
| @Test |
| public void testSslSessionIdHeaderPresent() throws Exception { |
| String session = "ssl-session"; |
| mockRequest.setHeader(valve.getSslSessionIdHeader(), session); |
| |
| valve.invoke(mockRequest, null); |
| |
| Assert.assertEquals(session, mockRequest.getAttribute(Globals.SSL_SESSION_ID_ATTR)); |
| } |
| |
| |
| @Test |
| public void testSslCipherUserKeySizeHeaderPresent() throws Exception { |
| Integer keySize = Integer.valueOf(452); |
| mockRequest.setHeader(valve.getSslCipherUserKeySizeHeader(), String.valueOf(keySize)); |
| |
| valve.invoke(mockRequest, null); |
| |
| Assert.assertEquals(keySize, mockRequest.getAttribute(Globals.KEY_SIZE_ATTR)); |
| } |
| |
| |
| @Test(expected = NumberFormatException.class) |
| public void testSslCipherUserKeySizeHeaderBadFormat() throws Exception { |
| mockRequest.setHeader(valve.getSslCipherUserKeySizeHeader(), "not-an-integer"); |
| |
| try { |
| valve.invoke(mockRequest, null); |
| } catch (NumberFormatException e) { |
| Assert.assertNull(mockRequest.getAttribute(Globals.KEY_SIZE_ATTR)); |
| throw e; |
| } |
| } |
| |
| |
| private static String certificateSingleLine(String[] lines, String separator) { |
| StringBuilder singleSpaced = new StringBuilder(); |
| for (String current : lines) { |
| singleSpaced.append(current).append(separator); |
| } |
| singleSpaced.deleteCharAt(singleSpaced.length() - 1); |
| return singleSpaced.toString(); |
| } |
| |
| |
| private static String certificateSingleLine(String separator) { |
| return certificateSingleLine(CERTIFICATE_LINES, separator); |
| } |
| |
| |
| private void assertCertificateParsed() throws Exception { |
| TesterLogValidationFilter f = TesterLogValidationFilter.add(null, "", null, |
| "org.apache.catalina.valves.SSLValve"); |
| |
| EasyMock.verify(mockNext); |
| |
| X509Certificate[] certificates = (X509Certificate[]) mockRequest.getAttribute(Globals.CERTIFICATES_ATTR); |
| Assert.assertNotNull(certificates); |
| Assert.assertEquals(1, certificates.length); |
| Assert.assertNotNull(certificates[0]); |
| Assert.assertEquals(0, f.getMessageCount()); |
| } |
| } |