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 <han...@users.noreply.github.com>
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());
+    }
+}


Reply via email to