This is an automated email from the ASF dual-hosted git repository.
pzampino pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/knox.git
The following commit(s) were added to refs/heads/master by this push:
new 8bb386ff4 KNOX-3172: New intercepting socket and outputstream to
handle FIPS bouncycastle exception, set max connections for
PoolingHttpClientConnectionManager (#1065)
8bb386ff4 is described below
commit 8bb386ff400ed2d065ee2a3856c5a2654ca5a408
Author: hanicz <[email protected]>
AuthorDate: Wed Jul 30 21:14:36 2025 +0200
KNOX-3172: New intercepting socket and outputstream to handle FIPS
bouncycastle exception, set max connections for
PoolingHttpClientConnectionManager (#1065)
---
.../apache/knox/gateway/SpiGatewayMessages.java | 6 +
.../gateway/dispatch/DefaultHttpClientFactory.java | 25 +-
.../BCInterceptingConnectionSocketFactory.java | 47 ++++
.../gateway/fips/BCInterceptingOutputStream.java | 100 ++++++++
.../knox/gateway/fips/BCInterceptingSocket.java | 275 +++++++++++++++++++++
.../gateway/fips/FipsConnectionManagerFactory.java | 51 ++++
.../org/apache/knox/gateway/fips/FipsUtils.java | 27 ++
.../fips/BCInterceptingOutputStreamTest.java | 180 ++++++++++++++
.../fips/FipsConnectionManagerFactoryTest.java | 33 +++
.../apache/knox/gateway/fips/FipsUtilsTest.java | 44 ++++
10 files changed, 780 insertions(+), 8 deletions(-)
diff --git
a/gateway-spi/src/main/java/org/apache/knox/gateway/SpiGatewayMessages.java
b/gateway-spi/src/main/java/org/apache/knox/gateway/SpiGatewayMessages.java
index 4440069d8..1037bffc4 100644
--- a/gateway-spi/src/main/java/org/apache/knox/gateway/SpiGatewayMessages.java
+++ b/gateway-spi/src/main/java/org/apache/knox/gateway/SpiGatewayMessages.java
@@ -138,4 +138,10 @@ public interface SpiGatewayMessages {
@Message( level = MessageLevel.ERROR, text = "Unable to close SSE producer" )
void sseProducerCloseError(@StackTrace(level=MessageLevel.ERROR) Exception
e);
+
+ @Message( level = MessageLevel.INFO, text = "BouncyCastle handleClose error,
ignoring exception" )
+ void ignoreHandleCloseError();
+
+ @Message(level = MessageLevel.INFO, text = "FIPS environment, configuring
intercepting socket")
+ void configureInterceptingSocket();
}
diff --git
a/gateway-spi/src/main/java/org/apache/knox/gateway/dispatch/DefaultHttpClientFactory.java
b/gateway-spi/src/main/java/org/apache/knox/gateway/dispatch/DefaultHttpClientFactory.java
index cb658c8fe..206a349e1 100644
---
a/gateway-spi/src/main/java/org/apache/knox/gateway/dispatch/DefaultHttpClientFactory.java
+++
b/gateway-spi/src/main/java/org/apache/knox/gateway/dispatch/DefaultHttpClientFactory.java
@@ -30,6 +30,8 @@ import javax.servlet.FilterConfig;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.ssl.SSLContextBuilder;
+import org.apache.knox.gateway.fips.FipsConnectionManagerFactory;
+import org.apache.knox.gateway.fips.FipsUtils;
import org.apache.knox.gateway.services.ServiceType;
import org.apache.knox.gateway.services.security.AliasService;
import org.apache.knox.gateway.services.security.KeystoreService;
@@ -91,11 +93,8 @@ public class DefaultHttpClientFactory implements
HttpClientFactory {
builder = HttpClients.custom();
}
- // Conditionally set a custom SSLContext
SSLContext sslContext = createSSLContext(services, filterConfig,
serviceRole);
- if(sslContext != null) {
- builder.setSSLSocketFactory(new SSLConnectionSocketFactory(sslContext));
- }
+ setSSLSocketFactory(sslContext, filterConfig, builder);
if
(Boolean.parseBoolean(System.getProperty(GatewayConfig.HADOOP_KERBEROS_SECURED)))
{
CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
@@ -117,10 +116,6 @@ public class DefaultHttpClientFactory implements
HttpClientFactory {
builder.setRedirectStrategy( new NeverRedirectStrategy() );
builder.setRetryHandler( new NeverRetryHandler() );
- int maxConnections = getMaxConnections( filterConfig );
- builder.setMaxConnTotal( maxConnections );
- builder.setMaxConnPerRoute( maxConnections );
-
builder.setDefaultRequestConfig(getRequestConfig(filterConfig,
serviceRole));
// See KNOX-1530 for details
@@ -143,6 +138,20 @@ public class DefaultHttpClientFactory implements
HttpClientFactory {
return builder.build();
}
+ private void setSSLSocketFactory(SSLContext sslContext, FilterConfig
filterConfig, HttpClientBuilder builder) {
+ int maxConnections = getMaxConnections(filterConfig);
+ if (FipsUtils.isFipsEnabledWithBCProvider()) {
+
builder.setConnectionManager(FipsConnectionManagerFactory.createConnectionManager(sslContext,
maxConnections));
+ } else {
+ builder.setMaxConnTotal(maxConnections);
+ builder.setMaxConnPerRoute(maxConnections);
+ // Conditionally set a custom SSLContext
+ if (sslContext != null) {
+ builder.setSSLSocketFactory(new
SSLConnectionSocketFactory(sslContext));
+ }
+ }
+ }
+
private boolean doesRetryParamExist(final FilterConfig filterConfig) {
return filterConfig.getInitParameter(PARAMETER_RETRY_COUNT) != null
&& StringUtils
diff --git
a/gateway-spi/src/main/java/org/apache/knox/gateway/fips/BCInterceptingConnectionSocketFactory.java
b/gateway-spi/src/main/java/org/apache/knox/gateway/fips/BCInterceptingConnectionSocketFactory.java
new file mode 100644
index 000000000..e7875aef4
--- /dev/null
+++
b/gateway-spi/src/main/java/org/apache/knox/gateway/fips/BCInterceptingConnectionSocketFactory.java
@@ -0,0 +1,47 @@
+/*
+ * 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.knox.gateway.fips;
+
+import org.apache.http.HttpHost;
+import org.apache.http.conn.socket.ConnectionSocketFactory;
+import org.apache.http.protocol.HttpContext;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+
+public class BCInterceptingConnectionSocketFactory implements
ConnectionSocketFactory {
+ private final ConnectionSocketFactory delegate;
+
+ public BCInterceptingConnectionSocketFactory(ConnectionSocketFactory
delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public Socket createSocket(HttpContext context) throws IOException {
+ return new BCInterceptingSocket(delegate.createSocket(context));
+ }
+
+ @Override
+ public Socket connectSocket(int connectTimeout, Socket socket, HttpHost
host,
+ InetSocketAddress remoteAddress,
InetSocketAddress localAddress,
+ HttpContext context) throws IOException {
+ Socket connected = delegate.connectSocket(connectTimeout, socket,
host, remoteAddress, localAddress, context);
+ return connected instanceof BCInterceptingSocket ? connected : new
BCInterceptingSocket(connected);
+ }
+}
diff --git
a/gateway-spi/src/main/java/org/apache/knox/gateway/fips/BCInterceptingOutputStream.java
b/gateway-spi/src/main/java/org/apache/knox/gateway/fips/BCInterceptingOutputStream.java
new file mode 100644
index 000000000..e6be1435e
--- /dev/null
+++
b/gateway-spi/src/main/java/org/apache/knox/gateway/fips/BCInterceptingOutputStream.java
@@ -0,0 +1,100 @@
+/*
+ * 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.knox.gateway.fips;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.knox.gateway.SpiGatewayMessages;
+import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.SocketException;
+
+public class BCInterceptingOutputStream extends OutputStream {
+
+ private static final SpiGatewayMessages LOG =
MessagesFactory.get(SpiGatewayMessages.class);
+ private final OutputStream delegate;
+ private static final String BOUNCYCASTLE_TLS_PROTOCOL_NAME =
"org.bouncycastle.tls.TlsProtocol";
+ private static final String BOUNCYCASTLE_HANDLE_CLOSE_METHOD =
"handleClose";
+ private static final String BROKEN_PIPE_MESSAGE = "Broken pipe";
+
+ public BCInterceptingOutputStream(OutputStream delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ try {
+ delegate.write(b);
+ } catch (SocketException e) {
+ handleSocketException(e);
+ }
+ }
+
+ @Override
+ public void write(byte[] b) throws IOException {
+ try {
+ delegate.write(b);
+ } catch (SocketException e) {
+ handleSocketException(e);
+ }
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ try {
+ delegate.write(b, off, len);
+ } catch (SocketException e) {
+ handleSocketException(e);
+ }
+ }
+
+ @Override
+ public void flush() throws IOException {
+ delegate.flush();
+ }
+
+ @Override
+ public void close() throws IOException {
+ delegate.close();
+ }
+
+ /**
+ * This method swallows the socketException if
org.bouncycastle.tls.TlsProtocol.handleClose method is in the stacktrace.
+ * This exception is thrown with 'Broken Pipe' message on FIPS
environments where the BouncyCastle provider is used.
+ * @param socketException The exception thrown
+ * @throws SocketException The exception is thrown again if it's not the
BouncyCastle one.
+ */
+ private void handleSocketException(SocketException socketException) throws
SocketException {
+ boolean exceptionIgnored = false;
+ if (socketException.getMessage() != null &&
StringUtils.containsIgnoreCase(socketException.getMessage(),
BROKEN_PIPE_MESSAGE)) {
+ StackTraceElement[] stackTrace = socketException.getStackTrace();
+ for (StackTraceElement ste : stackTrace) {
+ if (BOUNCYCASTLE_TLS_PROTOCOL_NAME.equals(ste.getClassName()))
{
+ if
(BOUNCYCASTLE_HANDLE_CLOSE_METHOD.equals(ste.getMethodName())) {
+ LOG.ignoreHandleCloseError();
+ exceptionIgnored = true;
+ }
+ }
+ }
+ }
+ if (!exceptionIgnored) {
+ throw socketException;
+ }
+ }
+}
diff --git
a/gateway-spi/src/main/java/org/apache/knox/gateway/fips/BCInterceptingSocket.java
b/gateway-spi/src/main/java/org/apache/knox/gateway/fips/BCInterceptingSocket.java
new file mode 100644
index 000000000..636607565
--- /dev/null
+++
b/gateway-spi/src/main/java/org/apache/knox/gateway/fips/BCInterceptingSocket.java
@@ -0,0 +1,275 @@
+/*
+ * 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.knox.gateway.fips;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.net.SocketException;
+import java.net.SocketOption;
+import java.nio.channels.SocketChannel;
+import java.util.Set;
+
+public class BCInterceptingSocket extends Socket {
+ private final Socket delegate;
+
+ public BCInterceptingSocket(Socket delegate) throws IOException {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public OutputStream getOutputStream() throws IOException {
+ OutputStream originalOutputStream = delegate.getOutputStream();
+ return new BCInterceptingOutputStream(originalOutputStream);
+ }
+
+ @Override
+ public InputStream getInputStream() throws IOException {
+ return delegate.getInputStream();
+ }
+
+ @Override
+ public void connect(SocketAddress endpoint) throws IOException {
+ delegate.connect(endpoint);
+ }
+
+ @Override
+ public void connect(SocketAddress endpoint, int timeout) throws
IOException {
+ delegate.connect(endpoint, timeout);
+ }
+
+ @Override
+ public void bind(SocketAddress bindpoint) throws IOException {
+ delegate.bind(bindpoint);
+ }
+
+ @Override
+ public void close() throws IOException {
+ delegate.close();
+ }
+
+ @Override
+ public boolean isConnected() {
+ return delegate.isConnected();
+ }
+
+ @Override
+ public boolean isClosed() {
+ return delegate.isClosed();
+ }
+
+ @Override
+ public boolean isInputShutdown() {
+ return delegate.isInputShutdown();
+ }
+
+ @Override
+ public boolean isOutputShutdown() {
+ return delegate.isOutputShutdown();
+ }
+
+ @Override
+ public void shutdownInput() throws IOException {
+ delegate.shutdownInput();
+ }
+
+ @Override
+ public void shutdownOutput() throws IOException {
+ delegate.shutdownOutput();
+ }
+
+ @Override
+ public InetAddress getInetAddress() {
+ return delegate.getInetAddress();
+ }
+
+ @Override
+ public InetAddress getLocalAddress() {
+ return delegate.getLocalAddress();
+ }
+
+ @Override
+ public int getPort() {
+ return delegate.getPort();
+ }
+
+ @Override
+ public int getLocalPort() {
+ return delegate.getLocalPort();
+ }
+
+ @Override
+ public SocketAddress getRemoteSocketAddress() {
+ return delegate.getRemoteSocketAddress();
+ }
+
+ @Override
+ public boolean isBound() {
+ return delegate.isBound();
+ }
+
+ @Override
+ public boolean getReuseAddress() throws SocketException {
+ return delegate.getReuseAddress();
+ }
+
+ @Override
+ public void setReuseAddress(boolean on) throws SocketException {
+ delegate.setReuseAddress(on);
+ }
+
+ @Override
+ public int getTrafficClass() throws SocketException {
+ return delegate.getTrafficClass();
+ }
+
+ @Override
+ public void setTrafficClass(int tc) throws SocketException {
+ delegate.setTrafficClass(tc);
+ }
+
+ @Override
+ public boolean getKeepAlive() throws SocketException {
+ return delegate.getKeepAlive();
+ }
+
+ @Override
+ public void setKeepAlive(boolean on) throws SocketException {
+ delegate.setKeepAlive(on);
+ }
+
+ @Override
+ public SocketAddress getLocalSocketAddress() {
+ return delegate.getLocalSocketAddress();
+ }
+
+ @Override
+ public SocketChannel getChannel() {
+ return delegate.getChannel();
+ }
+
+ @Override
+ public void setTcpNoDelay(boolean on) throws SocketException {
+ delegate.setTcpNoDelay(on);
+ }
+
+ @Override
+ public boolean getTcpNoDelay() throws SocketException {
+ return delegate.getTcpNoDelay();
+ }
+
+ @Override
+ public void setSoLinger(boolean on, int linger) throws SocketException {
+ delegate.setSoLinger(on, linger);
+ }
+
+ @Override
+ public int getSoLinger() throws SocketException {
+ return delegate.getSoLinger();
+ }
+
+ @Override
+ public void sendUrgentData(int data) throws IOException {
+ delegate.sendUrgentData(data);
+ }
+
+ @Override
+ public void setOOBInline(boolean on) throws SocketException {
+ delegate.setOOBInline(on);
+ }
+
+ @Override
+ public boolean getOOBInline() throws SocketException {
+ return delegate.getOOBInline();
+ }
+
+ @Override
+ public void setSoTimeout(int timeout) throws SocketException {
+ delegate.setSoTimeout(timeout);
+ }
+
+ @Override
+ public int getSoTimeout() throws SocketException {
+ return delegate.getSoTimeout();
+ }
+
+ @Override
+ public void setSendBufferSize(int size) throws SocketException {
+ delegate.setSendBufferSize(size);
+ }
+
+ @Override
+ public int getSendBufferSize() throws SocketException {
+ return delegate.getSendBufferSize();
+ }
+
+ @Override
+ public void setReceiveBufferSize(int size) throws SocketException {
+ delegate.setReceiveBufferSize(size);
+ }
+
+ @Override
+ public int getReceiveBufferSize() throws SocketException {
+ return delegate.getReceiveBufferSize();
+ }
+
+ @Override
+ public void setPerformancePreferences(int connectionTime,
+ int latency,
+ int bandwidth) {
+ delegate.setPerformancePreferences(connectionTime, latency, bandwidth);
+ }
+
+ /**
+ * This method is only available in JDK9+ therefor reflection is used to
call it.
+ */
+ @SuppressWarnings({"PMD.MissingOverride", "unchecked"})
+ public Set<SocketOption<?>> supportedOptions() {
+ return invokeDelegateMethod("supportedOptions", new Class<?>[]{});
+ }
+
+ /**
+ * This method is only available in JDK9+ therefor reflection is used to
call it.
+ */
+ @SuppressWarnings({"PMD.MissingOverride", "unchecked"})
+ public <T> T getOption(SocketOption<T> name) throws IOException {
+ return invokeDelegateMethod("getOption", new
Class<?>[]{SocketOption.class}, name);
+ }
+
+
+ /**
+ * This method is only available in JDK9+ therefor reflection is used to
call it.
+ */
+ @SuppressWarnings("PMD.MissingOverride")
+ public <T> Socket setOption(SocketOption<T> name, T value) throws
IOException {
+ return invokeDelegateMethod("setOption", new
Class<?>[]{SocketOption.class, Object.class}, name, value);
+ }
+
+ @SuppressWarnings("unchecked")
+ private <T> T invokeDelegateMethod(String methodName, Class<?>[]
parameterTypes, Object... args) {
+ try {
+ return (T) delegate.getClass().getMethod(methodName,
parameterTypes).invoke(delegate, args);
+ } catch (NoSuchMethodException | IllegalAccessException |
InvocationTargetException e) {
+ throw new UnsupportedOperationException("Socket option not
supported", e);
+ }
+ }
+}
diff --git
a/gateway-spi/src/main/java/org/apache/knox/gateway/fips/FipsConnectionManagerFactory.java
b/gateway-spi/src/main/java/org/apache/knox/gateway/fips/FipsConnectionManagerFactory.java
new file mode 100644
index 000000000..d201a8fc2
--- /dev/null
+++
b/gateway-spi/src/main/java/org/apache/knox/gateway/fips/FipsConnectionManagerFactory.java
@@ -0,0 +1,51 @@
+/*
+ * 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.knox.gateway.fips;
+
+import org.apache.http.config.Registry;
+import org.apache.http.config.RegistryBuilder;
+import org.apache.http.conn.socket.ConnectionSocketFactory;
+import org.apache.http.conn.socket.PlainConnectionSocketFactory;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+import org.apache.knox.gateway.SpiGatewayMessages;
+import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+
+import javax.net.ssl.SSLContext;
+
+public class FipsConnectionManagerFactory {
+
+ private static final SpiGatewayMessages LOG =
MessagesFactory.get(SpiGatewayMessages.class);
+
+ public static PoolingHttpClientConnectionManager
createConnectionManager(SSLContext sslContext, int maxConnections) {
+ LOG.configureInterceptingSocket();
+ BCInterceptingConnectionSocketFactory
BCInterceptingConnectionSocketFactory = sslContext != null ?
+ new BCInterceptingConnectionSocketFactory(new
SSLConnectionSocketFactory(sslContext))
+ : new
BCInterceptingConnectionSocketFactory(SSLConnectionSocketFactory.getSocketFactory());
+
+ Registry<ConnectionSocketFactory> socketFactoryRegistry =
RegistryBuilder.<ConnectionSocketFactory>create()
+ .register("http", new
BCInterceptingConnectionSocketFactory(PlainConnectionSocketFactory.getSocketFactory()))
+ .register("https", BCInterceptingConnectionSocketFactory)
+ .build();
+
+ PoolingHttpClientConnectionManager connManager = new
PoolingHttpClientConnectionManager(socketFactoryRegistry);
+ connManager.setMaxTotal(maxConnections);
+ connManager.setDefaultMaxPerRoute(maxConnections);
+ return connManager;
+ }
+}
diff --git
a/gateway-spi/src/main/java/org/apache/knox/gateway/fips/FipsUtils.java
b/gateway-spi/src/main/java/org/apache/knox/gateway/fips/FipsUtils.java
new file mode 100644
index 000000000..e586e971e
--- /dev/null
+++ b/gateway-spi/src/main/java/org/apache/knox/gateway/fips/FipsUtils.java
@@ -0,0 +1,27 @@
+/*
+ * 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.knox.gateway.fips;
+
+public class FipsUtils {
+
+ private static final String FIPS_SYSTEM_PROPERTY =
"com.safelogic.cryptocomply.fips.approved_only";
+
+ public static boolean isFipsEnabledWithBCProvider() {
+ return Boolean.parseBoolean(System.getProperty(FIPS_SYSTEM_PROPERTY));
+ }
+}
diff --git
a/gateway-spi/src/test/java/org/apache/knox/gateway/fips/BCInterceptingOutputStreamTest.java
b/gateway-spi/src/test/java/org/apache/knox/gateway/fips/BCInterceptingOutputStreamTest.java
new file mode 100644
index 000000000..50e5ecb81
--- /dev/null
+++
b/gateway-spi/src/test/java/org/apache/knox/gateway/fips/BCInterceptingOutputStreamTest.java
@@ -0,0 +1,180 @@
+/*
+ * 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.knox.gateway.fips;
+
+import org.easymock.EasyMock;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.SocketException;
+
+public class BCInterceptingOutputStreamTest {
+
+ @Test
+ public void writeExceptionIgnoredTest() throws IOException {
+ OutputStream outputStream =
EasyMock.createNiceMock(OutputStream.class);
+ BCInterceptingOutputStream BCInterceptingOutputStream =
+ new BCInterceptingOutputStream(outputStream);
+ SocketException socketException =
EasyMock.createNiceMock(SocketException.class);
+ StackTraceElement ste = new
StackTraceElement("org.bouncycastle.tls.TlsProtocol", "handleClose", null, 1);
+ StackTraceElement[] steArray = new StackTraceElement[1];
+ steArray[0] = ste;
+
+ outputStream.write(10);
+ EasyMock.expectLastCall().andThrow(socketException);
+ EasyMock.expect(socketException.getMessage()).andReturn("Broken pipe
(Write failed)").times(2);
+ EasyMock.expect(socketException.getStackTrace()).andReturn(steArray);
+ EasyMock.replay(outputStream, socketException);
+
+ BCInterceptingOutputStream.write(10);
+ }
+
+ @Test
+ public void writeByteArrayExceptionIgnoredTest() throws IOException {
+ OutputStream outputStream =
EasyMock.createNiceMock(OutputStream.class);
+ BCInterceptingOutputStream BCInterceptingOutputStream =
+ new BCInterceptingOutputStream(outputStream);
+ SocketException socketException =
EasyMock.createNiceMock(SocketException.class);
+ StackTraceElement ste = new
StackTraceElement("org.bouncycastle.tls.TlsProtocol", "handleClose", null, 1);
+ StackTraceElement[] steArray = new StackTraceElement[1];
+ steArray[0] = ste;
+
+ outputStream.write(new byte[]{10});
+ EasyMock.expectLastCall().andThrow(socketException);
+ EasyMock.expect(socketException.getMessage()).andReturn("Broken pipe
(Write failed)").times(2);
+ EasyMock.expect(socketException.getStackTrace()).andReturn(steArray);
+ EasyMock.replay(outputStream, socketException);
+
+ BCInterceptingOutputStream.write(new byte[]{10});
+ }
+
+ @Test
+ public void writeByteArrayOffsetExceptionIgnoredTest() throws IOException {
+ OutputStream outputStream =
EasyMock.createNiceMock(OutputStream.class);
+ BCInterceptingOutputStream BCInterceptingOutputStream =
+ new BCInterceptingOutputStream(outputStream);
+ SocketException socketException =
EasyMock.createNiceMock(SocketException.class);
+ StackTraceElement ste = new
StackTraceElement("org.bouncycastle.tls.TlsProtocol", "handleClose", null, 1);
+ StackTraceElement[] steArray = new StackTraceElement[1];
+ steArray[0] = ste;
+
+ outputStream.write(new byte[]{10}, 10, 10);
+ EasyMock.expectLastCall().andThrow(socketException);
+ EasyMock.expect(socketException.getMessage()).andReturn("Broken pipe
(Write failed)").times(2);
+ EasyMock.expect(socketException.getStackTrace()).andReturn(steArray);
+ EasyMock.replay(outputStream, socketException);
+
+ BCInterceptingOutputStream.write(new byte[]{10}, 10, 10);
+ }
+
+ @Test
+ public void writeExceptionIgnoredJDK17Test() throws IOException {
+ OutputStream outputStream =
EasyMock.createNiceMock(OutputStream.class);
+ BCInterceptingOutputStream BCInterceptingOutputStream =
+ new BCInterceptingOutputStream(outputStream);
+ SocketException socketException =
EasyMock.createNiceMock(SocketException.class);
+ StackTraceElement ste = new
StackTraceElement("org.bouncycastle.tls.TlsProtocol", "handleClose", null, 1);
+ StackTraceElement[] steArray = new StackTraceElement[1];
+ steArray[0] = ste;
+
+ outputStream.write(10);
+ EasyMock.expectLastCall().andThrow(socketException);
+ EasyMock.expect(socketException.getMessage()).andReturn("Broken
pipe").times(2);
+ EasyMock.expect(socketException.getStackTrace()).andReturn(steArray);
+ EasyMock.replay(outputStream, socketException);
+
+ BCInterceptingOutputStream.write(10);
+ }
+
+ @Test(expected = SocketException.class)
+ public void writeDifferentMessageTest() throws IOException {
+ OutputStream outputStream =
EasyMock.createNiceMock(OutputStream.class);
+ BCInterceptingOutputStream BCInterceptingOutputStream =
+ new BCInterceptingOutputStream(outputStream);
+ SocketException socketException =
EasyMock.createNiceMock(SocketException.class);
+ StackTraceElement ste = new
StackTraceElement("org.bouncycastle.tls.TlsProtocol", "handleClose", null, 1);
+ StackTraceElement[] steArray = new StackTraceElement[1];
+ steArray[0] = ste;
+
+ outputStream.write(10);
+ EasyMock.expectLastCall().andThrow(socketException);
+ EasyMock.expect(socketException.getMessage()).andReturn("Non Broken
message (Write failed)").times(2);
+ EasyMock.expect(socketException.getStackTrace()).andReturn(steArray);
+ EasyMock.replay(outputStream, socketException);
+
+ BCInterceptingOutputStream.write(10);
+ }
+
+ @Test(expected = SocketException.class)
+ public void writeDifferentClassNameTest() throws IOException {
+ OutputStream outputStream =
EasyMock.createNiceMock(OutputStream.class);
+ BCInterceptingOutputStream BCInterceptingOutputStream =
+ new BCInterceptingOutputStream(outputStream);
+ SocketException socketException =
EasyMock.createNiceMock(SocketException.class);
+ StackTraceElement ste = new
StackTraceElement("org.bouncycastle.tls.NonTlsProtocol", "handleClose", null,
1);
+ StackTraceElement[] steArray = new StackTraceElement[1];
+ steArray[0] = ste;
+
+ outputStream.write(10);
+ EasyMock.expectLastCall().andThrow(socketException);
+ EasyMock.expect(socketException.getMessage()).andReturn("Broken pipe
(Write failed)").times(2);
+ EasyMock.expect(socketException.getStackTrace()).andReturn(steArray);
+ EasyMock.replay(outputStream, socketException);
+
+ BCInterceptingOutputStream.write(10);
+ }
+
+ @Test(expected = SocketException.class)
+ public void writeDifferentMethodNameTest() throws IOException {
+ OutputStream outputStream =
EasyMock.createNiceMock(OutputStream.class);
+ BCInterceptingOutputStream BCInterceptingOutputStream =
+ new BCInterceptingOutputStream(outputStream);
+ SocketException socketException =
EasyMock.createNiceMock(SocketException.class);
+ StackTraceElement ste = new
StackTraceElement("org.bouncycastle.tls.TlsProtocol", "nonHandleClose", null,
1);
+ StackTraceElement[] steArray = new StackTraceElement[1];
+ steArray[0] = ste;
+
+ outputStream.write(10);
+ EasyMock.expectLastCall().andThrow(socketException);
+ EasyMock.expect(socketException.getMessage()).andReturn("Broken pipe
(Write failed)").times(2);
+ EasyMock.expect(socketException.getStackTrace()).andReturn(steArray);
+ EasyMock.replay(outputStream, socketException);
+
+ BCInterceptingOutputStream.write(10);
+ }
+
+ @Test(expected = IOException.class)
+ public void writeNonSocketExceptionTest() throws IOException {
+ OutputStream outputStream =
EasyMock.createNiceMock(OutputStream.class);
+ BCInterceptingOutputStream BCInterceptingOutputStream =
+ new BCInterceptingOutputStream(outputStream);
+ IOException ioException = EasyMock.createNiceMock(IOException.class);
+ StackTraceElement ste = new
StackTraceElement("org.bouncycastle.tls.TlsProtocol", "handleClose", null, 1);
+ StackTraceElement[] steArray = new StackTraceElement[1];
+ steArray[0] = ste;
+
+ outputStream.write(10);
+ EasyMock.expectLastCall().andThrow(ioException);
+ EasyMock.expect(ioException.getMessage()).andReturn("Broken pipe
(Write failed)").times(2);
+ EasyMock.expect(ioException.getStackTrace()).andReturn(steArray);
+ EasyMock.replay(outputStream, ioException);
+
+ BCInterceptingOutputStream.write(10);
+ }
+}
diff --git
a/gateway-spi/src/test/java/org/apache/knox/gateway/fips/FipsConnectionManagerFactoryTest.java
b/gateway-spi/src/test/java/org/apache/knox/gateway/fips/FipsConnectionManagerFactoryTest.java
new file mode 100644
index 000000000..83b1047b2
--- /dev/null
+++
b/gateway-spi/src/test/java/org/apache/knox/gateway/fips/FipsConnectionManagerFactoryTest.java
@@ -0,0 +1,33 @@
+/*
+ * 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.knox.gateway.fips;
+
+import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class FipsConnectionManagerFactoryTest {
+
+ @Test
+ public void testCreateConnectionManager() {
+ PoolingHttpClientConnectionManager connectionManager =
FipsConnectionManagerFactory.createConnectionManager(null, 10);
+ assertEquals(10, connectionManager.getMaxTotal());
+ assertEquals(10, connectionManager.getDefaultMaxPerRoute());
+ }
+}
diff --git
a/gateway-spi/src/test/java/org/apache/knox/gateway/fips/FipsUtilsTest.java
b/gateway-spi/src/test/java/org/apache/knox/gateway/fips/FipsUtilsTest.java
new file mode 100644
index 000000000..df2364b82
--- /dev/null
+++ b/gateway-spi/src/test/java/org/apache/knox/gateway/fips/FipsUtilsTest.java
@@ -0,0 +1,44 @@
+/*
+ * 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.knox.gateway.fips;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class FipsUtilsTest {
+
+ @Test
+ public void testIsFipsEnabledWithBCProviderEmpty() {
+ System.clearProperty("com.safelogic.cryptocomply.fips.approved_only");
+ assertFalse(FipsUtils.isFipsEnabledWithBCProvider());
+ }
+
+ @Test
+ public void testIsFipsEnabledWithBCProviderSetToTrue() {
+ System.setProperty("com.safelogic.cryptocomply.fips.approved_only",
"true");
+ assertTrue(FipsUtils.isFipsEnabledWithBCProvider());
+ }
+
+ @Test
+ public void testIsFipsEnabledWithBCProviderSetToFalse() {
+ System.setProperty("com.safelogic.cryptocomply.fips.approved_only",
"false");
+ assertFalse(FipsUtils.isFipsEnabledWithBCProvider());
+ }
+}