>From Michael Blow <[email protected]>:

Michael Blow has uploaded this change for review. ( 
https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/21349?usp=email )


Change subject: [NO ISSUE][HYR][NET] Add TLS peer-certificate revocation hook
......................................................................

[NO ISSUE][HYR][NET] Add TLS peer-certificate revocation hook

- user model changes: no
- storage format changes: no
- interface changes: yes (INetworkSecurityConfig)

Details:
- Introduce ICertificateRevocationChecker (hyracks-api), a generic
  hook invoked after PKIX path validation with the verified chain and
  authType, so embedders can reject revoked peer certificates without
  coupling hyracks to their auth layer.
- Add INetworkSecurityConfig#getCertificateRevocationChecker(), a
  default (empty) accessor; existing configs are unaffected.
- Add RevocationCheckingTrustManager (hyracks-ipc), wrapping a delegate
  X509ExtendedTrustManager and consulting the checker in every
  checkClientTrusted/checkServerTrusted overload, enforcing revocation
  for both inbound and outbound (node-to-node) connections.
- NetworkSecurityManager wraps the PKIX trust managers with the checker
  when one is supplied by the config.
- Add RevocationCheckingTrustManagerTest covering delegate-then-checker
  ordering, exception propagation, and PKIX-failure short-circuiting.

Ext-ref: MB-72053
Co-Authored-By: Claude Opus 4.8 via Claude-UI <[email protected]>
Change-Id: I45c77d77e7ab2a0e5d827a6e8ee744d38b914a89
---
A 
hyracks-fullstack/hyracks/hyracks-api/src/main/java/org/apache/hyracks/api/network/ICertificateRevocationChecker.java
M 
hyracks-fullstack/hyracks/hyracks-api/src/main/java/org/apache/hyracks/api/network/INetworkSecurityConfig.java
M 
hyracks-fullstack/hyracks/hyracks-ipc/src/main/java/org/apache/hyracks/ipc/security/NetworkSecurityManager.java
A 
hyracks-fullstack/hyracks/hyracks-ipc/src/main/java/org/apache/hyracks/ipc/security/RevocationCheckingTrustManager.java
A 
hyracks-fullstack/hyracks/hyracks-ipc/src/test/java/org/apache/hyracks/ipc/security/RevocationCheckingTrustManagerTest.java
5 files changed, 354 insertions(+), 1 deletion(-)



  git pull ssh://asterix-gerrit.ics.uci.edu:29418/asterixdb 
refs/changes/49/21349/1

diff --git 
a/hyracks-fullstack/hyracks/hyracks-api/src/main/java/org/apache/hyracks/api/network/ICertificateRevocationChecker.java
 
b/hyracks-fullstack/hyracks/hyracks-api/src/main/java/org/apache/hyracks/api/network/ICertificateRevocationChecker.java
new file mode 100644
index 0000000..c3696ff
--- /dev/null
+++ 
b/hyracks-fullstack/hyracks/hyracks-api/src/main/java/org/apache/hyracks/api/network/ICertificateRevocationChecker.java
@@ -0,0 +1,42 @@
+/*
+ * 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.hyracks.api.network;
+
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+
+// import org.apache.hyracks.util.annotations.AiProvenance; // TODO(helios): 
uncomment with @AiProvenance below (absent on this branch)
+
+/**
+ * Hook invoked during the TLS handshake (after standard PKIX path validation) 
to check a peer certificate chain for
+ * revocation. Implementations are provided by the embedding application via
+ * {@link INetworkSecurityConfig#getCertificateRevocationChecker()} and are 
kept free of any application-specific types
+ * so {@code hyracks-ipc} stays decoupled from the auth layer.
+ */
+// @AiProvenance(agent = AiProvenance.Agent.CLAUDE_OPUS_4_8, tool = 
AiProvenance.Tool.CLAUDE_UI, contributionKind = 
AiProvenance.ContributionKind.GENERATED)
+@FunctionalInterface
+public interface ICertificateRevocationChecker {
+
+    /**
+     * @param chain the peer certificate chain that has already passed PKIX 
validation (end-entity first)
+     * @param authType the key-exchange / authentication type, as passed to 
the delegate {@code X509TrustManager}
+     * @throws CertificateException if the connection should be rejected on 
revocation grounds
+     */
+    void checkRevocation(X509Certificate[] chain, String authType) throws 
CertificateException;
+}
diff --git 
a/hyracks-fullstack/hyracks/hyracks-api/src/main/java/org/apache/hyracks/api/network/INetworkSecurityConfig.java
 
b/hyracks-fullstack/hyracks/hyracks-api/src/main/java/org/apache/hyracks/api/network/INetworkSecurityConfig.java
index 2e00e4d..cd993ac 100644
--- 
a/hyracks-fullstack/hyracks/hyracks-api/src/main/java/org/apache/hyracks/api/network/INetworkSecurityConfig.java
+++ 
b/hyracks-fullstack/hyracks/hyracks-api/src/main/java/org/apache/hyracks/api/network/INetworkSecurityConfig.java
@@ -24,6 +24,7 @@
 import java.security.KeyStore;
 import java.util.Optional;

+// import org.apache.hyracks.util.annotations.AiProvenance; // TODO(helios): 
uncomment with @AiProvenance below (absent on this branch)
 import io.netty.handler.ssl.ClientAuth;

 public interface INetworkSecurityConfig extends Serializable {
@@ -109,4 +110,15 @@
      * @return the optional bind address
      */
     Optional<InetAddress> getRMIBindAddress();
+
+    /**
+     * An optional hook to check peer certificates for revocation during the 
TLS handshake (after PKIX validation).
+     * When present, it is applied to both inbound and outbound node-to-node 
connections.
+     *
+     * @return the revocation checker, or {@link Optional#empty()} to disable 
revocation checking
+     */
+    // @AiProvenance(agent = AiProvenance.Agent.CLAUDE_OPUS_4_8, tool = 
AiProvenance.Tool.CLAUDE_UI, contributionKind = 
AiProvenance.ContributionKind.GENERATED)
+    default Optional<ICertificateRevocationChecker> 
getCertificateRevocationChecker() {
+        return Optional.empty();
+    }
 }
diff --git 
a/hyracks-fullstack/hyracks/hyracks-ipc/src/main/java/org/apache/hyracks/ipc/security/NetworkSecurityManager.java
 
b/hyracks-fullstack/hyracks/hyracks-ipc/src/main/java/org/apache/hyracks/ipc/security/NetworkSecurityManager.java
index d8f5cff..519c948 100644
--- 
a/hyracks-fullstack/hyracks/hyracks-ipc/src/main/java/org/apache/hyracks/ipc/security/NetworkSecurityManager.java
+++ 
b/hyracks-fullstack/hyracks/hyracks-ipc/src/main/java/org/apache/hyracks/ipc/security/NetworkSecurityManager.java
@@ -22,13 +22,18 @@
 import java.io.FileInputStream;
 import java.security.KeyStore;
 import java.security.SecureRandom;
+import java.util.Optional;

 import javax.net.ssl.KeyManagerFactory;
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.SSLEngine;
+import javax.net.ssl.TrustManager;
 import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509ExtendedTrustManager;

+import org.apache.hyracks.api.network.ICertificateRevocationChecker;
 import org.apache.hyracks.api.network.INetworkSecurityConfig;
+// import org.apache.hyracks.util.annotations.AiProvenance; // TODO(helios): 
uncomment with @AiProvenance below (absent on this branch)
 import org.apache.hyracks.api.network.INetworkSecurityManager;
 import org.apache.hyracks.api.network.ISocketChannelFactory;
 import org.apache.hyracks.ipc.sockets.PlainSocketChannelFactory;
@@ -104,8 +109,19 @@
                 trustStore = loadKeyStoreFromFile(password, 
config.getTrustStoreFile());
             }
             trustManagerFactory.init(trustStore);
+            final TrustManager[] trustManagers = 
trustManagerFactory.getTrustManagers();
+            // @AiProvenance(agent = AiProvenance.Agent.CLAUDE_OPUS_4_8, tool 
= AiProvenance.Tool.CLAUDE_UI, contributionKind = 
AiProvenance.ContributionKind.GENERATED)
+            final Optional<ICertificateRevocationChecker> revocationChecker = 
config.getCertificateRevocationChecker();
+            if (revocationChecker.isPresent()) {
+                for (int i = 0; i < trustManagers.length; i++) {
+                    if (trustManagers[i] instanceof X509ExtendedTrustManager) {
+                        trustManagers[i] = new RevocationCheckingTrustManager(
+                                (X509ExtendedTrustManager) trustManagers[i], 
revocationChecker.get());
+                    }
+                }
+            }
             SSLContext ctx = SSLContext.getInstance(TLS_VERSION);
-            ctx.init(keyManagerFactory.getKeyManagers(), 
trustManagerFactory.getTrustManagers(), new SecureRandom());
+            ctx.init(keyManagerFactory.getKeyManagers(), trustManagers, new 
SecureRandom());
             return ctx;
         } catch (Exception ex) {
             throw new IllegalStateException("Failed to create SSLEngine", ex);
diff --git 
a/hyracks-fullstack/hyracks/hyracks-ipc/src/main/java/org/apache/hyracks/ipc/security/RevocationCheckingTrustManager.java
 
b/hyracks-fullstack/hyracks/hyracks-ipc/src/main/java/org/apache/hyracks/ipc/security/RevocationCheckingTrustManager.java
new file mode 100644
index 0000000..2aff1c8
--- /dev/null
+++ 
b/hyracks-fullstack/hyracks/hyracks-ipc/src/main/java/org/apache/hyracks/ipc/security/RevocationCheckingTrustManager.java
@@ -0,0 +1,93 @@
+/*
+ * 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.hyracks.ipc.security;
+
+import java.net.Socket;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.X509ExtendedTrustManager;
+
+import org.apache.hyracks.api.network.ICertificateRevocationChecker;
+
+// import org.apache.hyracks.util.annotations.AiProvenance; // TODO(helios): 
uncomment with @AiProvenance below (absent on this branch)
+
+/**
+ * Wraps a delegate {@link X509ExtendedTrustManager} so that, after the 
delegate performs standard PKIX path validation,
+ * an {@link ICertificateRevocationChecker} is consulted to reject revoked 
peer certificates. Both
+ * {@code checkClientTrusted} and {@code checkServerTrusted} are intercepted, 
so revocation is enforced for both
+ * inbound and outbound connections (mirroring a {@code VerifyPeerCertificate} 
callback).
+ */
+// @AiProvenance(agent = AiProvenance.Agent.CLAUDE_OPUS_4_8, tool = 
AiProvenance.Tool.CLAUDE_UI, contributionKind = 
AiProvenance.ContributionKind.GENERATED)
+public class RevocationCheckingTrustManager extends X509ExtendedTrustManager {
+
+    private final X509ExtendedTrustManager delegate;
+    private final ICertificateRevocationChecker checker;
+
+    public RevocationCheckingTrustManager(X509ExtendedTrustManager delegate, 
ICertificateRevocationChecker checker) {
+        this.delegate = delegate;
+        this.checker = checker;
+    }
+
+    @Override
+    public void checkClientTrusted(X509Certificate[] chain, String authType) 
throws CertificateException {
+        delegate.checkClientTrusted(chain, authType);
+        checker.checkRevocation(chain, authType);
+    }
+
+    @Override
+    public void checkClientTrusted(X509Certificate[] chain, String authType, 
Socket socket)
+            throws CertificateException {
+        delegate.checkClientTrusted(chain, authType, socket);
+        checker.checkRevocation(chain, authType);
+    }
+
+    @Override
+    public void checkClientTrusted(X509Certificate[] chain, String authType, 
SSLEngine engine)
+            throws CertificateException {
+        delegate.checkClientTrusted(chain, authType, engine);
+        checker.checkRevocation(chain, authType);
+    }
+
+    @Override
+    public void checkServerTrusted(X509Certificate[] chain, String authType) 
throws CertificateException {
+        delegate.checkServerTrusted(chain, authType);
+        checker.checkRevocation(chain, authType);
+    }
+
+    @Override
+    public void checkServerTrusted(X509Certificate[] chain, String authType, 
Socket socket)
+            throws CertificateException {
+        delegate.checkServerTrusted(chain, authType, socket);
+        checker.checkRevocation(chain, authType);
+    }
+
+    @Override
+    public void checkServerTrusted(X509Certificate[] chain, String authType, 
SSLEngine engine)
+            throws CertificateException {
+        delegate.checkServerTrusted(chain, authType, engine);
+        checker.checkRevocation(chain, authType);
+    }
+
+    @Override
+    public X509Certificate[] getAcceptedIssuers() {
+        return delegate.getAcceptedIssuers();
+    }
+}
diff --git 
a/hyracks-fullstack/hyracks/hyracks-ipc/src/test/java/org/apache/hyracks/ipc/security/RevocationCheckingTrustManagerTest.java
 
b/hyracks-fullstack/hyracks/hyracks-ipc/src/test/java/org/apache/hyracks/ipc/security/RevocationCheckingTrustManagerTest.java
new file mode 100644
index 0000000..6b51f94
--- /dev/null
+++ 
b/hyracks-fullstack/hyracks/hyracks-ipc/src/test/java/org/apache/hyracks/ipc/security/RevocationCheckingTrustManagerTest.java
@@ -0,0 +1,190 @@
+/*
+ * 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.hyracks.ipc.security;
+
+import java.net.Socket;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.X509ExtendedTrustManager;
+
+import org.apache.hyracks.api.network.ICertificateRevocationChecker;
+// import org.apache.hyracks.util.annotations.AiProvenance; // TODO(helios): 
uncomment with @AiProvenance below (absent on this branch)
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+// @AiProvenance(agent = AiProvenance.Agent.CLAUDE_OPUS_4_8, tool = 
AiProvenance.Tool.CLAUDE_UI, contributionKind = 
AiProvenance.ContributionKind.TEST_GENERATED)
+public class RevocationCheckingTrustManagerTest {
+
+    private static final X509Certificate[] CHAIN = new X509Certificate[0];
+    private static final String AUTH_TYPE = "RSA";
+
+    private RecordingTrustManager delegate;
+    private RecordingChecker checker;
+    private RevocationCheckingTrustManager tm;
+
+    @Before
+    public void setup() {
+        delegate = new RecordingTrustManager();
+        checker = new RecordingChecker();
+        tm = new RevocationCheckingTrustManager(delegate, checker);
+    }
+
+    // -- success path: delegate (PKIX) runs first, then the revocation 
checker, for every overload --
+
+    @Test
+    public void checkClientTrusted_callsDelegateThenChecker() throws Exception 
{
+        tm.checkClientTrusted(CHAIN, AUTH_TYPE);
+        assertDelegateThenChecker("checkClientTrusted");
+    }
+
+    @Test
+    public void checkClientTrusted_socket_callsDelegateThenChecker() throws 
Exception {
+        tm.checkClientTrusted(CHAIN, AUTH_TYPE, (Socket) null);
+        assertDelegateThenChecker("checkClientTrusted");
+    }
+
+    @Test
+    public void checkClientTrusted_engine_callsDelegateThenChecker() throws 
Exception {
+        tm.checkClientTrusted(CHAIN, AUTH_TYPE, (SSLEngine) null);
+        assertDelegateThenChecker("checkClientTrusted");
+    }
+
+    @Test
+    public void checkServerTrusted_callsDelegateThenChecker() throws Exception 
{
+        tm.checkServerTrusted(CHAIN, AUTH_TYPE);
+        assertDelegateThenChecker("checkServerTrusted");
+    }
+
+    @Test
+    public void checkServerTrusted_socket_callsDelegateThenChecker() throws 
Exception {
+        tm.checkServerTrusted(CHAIN, AUTH_TYPE, (Socket) null);
+        assertDelegateThenChecker("checkServerTrusted");
+    }
+
+    @Test
+    public void checkServerTrusted_engine_callsDelegateThenChecker() throws 
Exception {
+        tm.checkServerTrusted(CHAIN, AUTH_TYPE, (SSLEngine) null);
+        assertDelegateThenChecker("checkServerTrusted");
+    }
+
+    // -- a revoked cert (checker throws) aborts the handshake --
+
+    @Test
+    public void checkerException_propagates() {
+        checker.toThrow = new CertificateException("revoked");
+        Assert.assertThrows(CertificateException.class, () -> 
tm.checkClientTrusted(CHAIN, AUTH_TYPE));
+        Assert.assertThrows(CertificateException.class, () -> 
tm.checkServerTrusted(CHAIN, AUTH_TYPE));
+    }
+
+    // -- if PKIX validation fails, the checker is never consulted --
+
+    @Test
+    public void delegateException_shortCircuitsChecker() {
+        delegate.toThrow = new CertificateException("not trusted");
+        Assert.assertThrows(CertificateException.class, () -> 
tm.checkClientTrusted(CHAIN, AUTH_TYPE));
+        Assert.assertEquals(0, checker.calls);
+    }
+
+    @Test
+    public void getAcceptedIssuers_delegates() {
+        Assert.assertSame(RecordingTrustManager.ISSUERS, 
tm.getAcceptedIssuers());
+    }
+
+    private void assertDelegateThenChecker(String expectedDelegateMethod) {
+        Assert.assertEquals("delegate should be called exactly once", 1, 
delegate.calls.size());
+        Assert.assertEquals(expectedDelegateMethod, delegate.calls.get(0));
+        Assert.assertEquals("checker should be called exactly once", 1, 
checker.calls);
+        Assert.assertSame("checker must receive the same chain", CHAIN, 
checker.lastChain);
+        Assert.assertEquals(AUTH_TYPE, checker.lastAuthType);
+    }
+
+    private static final class RecordingChecker implements 
ICertificateRevocationChecker {
+        int calls;
+        X509Certificate[] lastChain;
+        String lastAuthType;
+        CertificateException toThrow;
+
+        @Override
+        public void checkRevocation(X509Certificate[] chain, String authType) 
throws CertificateException {
+            calls++;
+            lastChain = chain;
+            lastAuthType = authType;
+            if (toThrow != null) {
+                throw toThrow;
+            }
+        }
+    }
+
+    private static final class RecordingTrustManager extends 
X509ExtendedTrustManager {
+        static final X509Certificate[] ISSUERS = new X509Certificate[0];
+        final List<String> calls = new ArrayList<>();
+        CertificateException toThrow;
+
+        private void record(String method) throws CertificateException {
+            calls.add(method);
+            if (toThrow != null) {
+                throw toThrow;
+            }
+        }
+
+        @Override
+        public void checkClientTrusted(X509Certificate[] chain, String 
authType) throws CertificateException {
+            record("checkClientTrusted");
+        }
+
+        @Override
+        public void checkClientTrusted(X509Certificate[] chain, String 
authType, Socket socket)
+                throws CertificateException {
+            record("checkClientTrusted");
+        }
+
+        @Override
+        public void checkClientTrusted(X509Certificate[] chain, String 
authType, SSLEngine engine)
+                throws CertificateException {
+            record("checkClientTrusted");
+        }
+
+        @Override
+        public void checkServerTrusted(X509Certificate[] chain, String 
authType) throws CertificateException {
+            record("checkServerTrusted");
+        }
+
+        @Override
+        public void checkServerTrusted(X509Certificate[] chain, String 
authType, Socket socket)
+                throws CertificateException {
+            record("checkServerTrusted");
+        }
+
+        @Override
+        public void checkServerTrusted(X509Certificate[] chain, String 
authType, SSLEngine engine)
+                throws CertificateException {
+            record("checkServerTrusted");
+        }
+
+        @Override
+        public X509Certificate[] getAcceptedIssuers() {
+            return ISSUERS;
+        }
+    }
+}

--
To view, visit https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/21349?usp=email
To unsubscribe, or for help writing mail filters, visit 
https://asterix-gerrit.ics.uci.edu/settings?usp=email

Gerrit-MessageType: newchange
Gerrit-Project: asterixdb
Gerrit-Branch: morpheus
Gerrit-Change-Id: I45c77d77e7ab2a0e5d827a6e8ee744d38b914a89
Gerrit-Change-Number: 21349
Gerrit-PatchSet: 1
Gerrit-Owner: Michael Blow <[email protected]>

Reply via email to