This is an automated email from the ASF dual-hosted git repository.

cdutz pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/plc4x.git


The following commit(s) were added to refs/heads/develop by this push:
     new 14944dbcd8 feat: Added a new PlcCertificateAuthentication type to the 
API module.
14944dbcd8 is described below

commit 14944dbcd8c826b9e4c0547cbadbc801f2ecd5ac
Author: Christofer Dutz <[email protected]>
AuthorDate: Fri Jan 9 18:33:55 2026 +0100

    feat: Added a new PlcCertificateAuthentication type to the API module.
---
 RELEASE_NOTES                                      |   1 +
 .../PlcCertificateAuthentication.java              | 136 ++++++++++++
 .../PlcCertificateAuthenticationTest.java          | 230 +++++++++++++++++++++
 3 files changed, 367 insertions(+)

diff --git a/RELEASE_NOTES b/RELEASE_NOTES
index a52a9c8d8c..f47f4b739f 100644
--- a/RELEASE_NOTES
+++ b/RELEASE_NOTES
@@ -10,6 +10,7 @@ New Features
 - Subscription of change-of-state values now supports
   providing a min time interval to prevent excessive
   notifications.
+- Added a new PlcCertificateAuthentication to the API module.
 
 Incompatible changes
 --------------------
diff --git 
a/plc4j/api/src/main/java/org/apache/plc4x/java/api/authentication/PlcCertificateAuthentication.java
 
b/plc4j/api/src/main/java/org/apache/plc4x/java/api/authentication/PlcCertificateAuthentication.java
new file mode 100644
index 0000000000..feb5f97db8
--- /dev/null
+++ 
b/plc4j/api/src/main/java/org/apache/plc4x/java/api/authentication/PlcCertificateAuthentication.java
@@ -0,0 +1,136 @@
+/*
+ * 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
+ *
+ *   https://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.plc4x.java.api.authentication;
+
+import java.security.KeyStore;
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * Authentication implementation for mutual TLS (mTLS) using client 
certificates.
+ * <p>
+ * This class provides client certificate authentication for TLS connections 
that require
+ * mutual authentication. The client certificate and private key are provided 
via a KeyStore.
+ * <p>
+ * Example usage:
+ * <pre>{@code
+ * KeyStore keyStore = KeyStore.getInstance("PKCS12");
+ * try (FileInputStream fis = new FileInputStream("client-cert.p12")) {
+ *     keyStore.load(fis, "keystorePassword".toCharArray());
+ * }
+ *
+ * PlcAuthentication auth = new PlcCertificateAuthentication(
+ *     keyStore,
+ *     "keystorePassword".toCharArray()
+ * );
+ *
+ * PlcConnection connection = driverManager.getConnection(connectionUrl, auth);
+ * }</pre>
+ */
+public class PlcCertificateAuthentication implements PlcAuthentication {
+
+    private final KeyStore keyStore;
+    private final char[] keyStorePassword;
+    private final String keyAlias;
+
+    /**
+     * Creates a new certificate authentication with the specified KeyStore.
+     * Uses the first key entry found in the KeyStore.
+     *
+     * @param keyStore         the KeyStore containing the client certificate 
and private key
+     * @param keyStorePassword the password to access the KeyStore and key 
entries
+     * @throws NullPointerException if keyStore or keyStorePassword is null
+     */
+    public PlcCertificateAuthentication(KeyStore keyStore, char[] 
keyStorePassword) {
+        this(keyStore, keyStorePassword, null);
+    }
+
+    /**
+     * Creates a new certificate authentication with the specified KeyStore 
and key alias.
+     *
+     * @param keyStore         the KeyStore containing the client certificate 
and private key
+     * @param keyStorePassword the password to access the KeyStore and key 
entries
+     * @param keyAlias         the alias of the key entry to use, or null to 
use the first key entry
+     * @throws NullPointerException if keyStore or keyStorePassword is null
+     */
+    public PlcCertificateAuthentication(KeyStore keyStore, char[] 
keyStorePassword, String keyAlias) {
+        Objects.requireNonNull(keyStore, "KeyStore must not be null");
+        Objects.requireNonNull(keyStorePassword, "KeyStore password must not 
be null");
+        this.keyStore = keyStore;
+        this.keyStorePassword = keyStorePassword.clone();
+        this.keyAlias = keyAlias;
+    }
+
+    /**
+     * Returns the KeyStore containing the client certificate and private key.
+     *
+     * @return the KeyStore
+     */
+    public KeyStore getKeyStore() {
+        return keyStore;
+    }
+
+    /**
+     * Returns the password for the KeyStore and key entries.
+     * Note: Returns a clone for security reasons.
+     *
+     * @return a copy of the KeyStore password
+     */
+    public char[] getKeyStorePassword() {
+        return keyStorePassword.clone();
+    }
+
+    /**
+     * Returns the alias of the key entry to use.
+     *
+     * @return the key alias, or null if the first key entry should be used
+     */
+    public String getKeyAlias() {
+        return keyAlias;
+    }
+
+    @Override
+    public final boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof PlcCertificateAuthentication that)) {
+            return false;
+        }
+        return Objects.equals(keyStore, that.keyStore) &&
+            Arrays.equals(keyStorePassword, that.keyStorePassword) &&
+            Objects.equals(keyAlias, that.keyAlias);
+    }
+
+    @Override
+    public final int hashCode() {
+        int result = Objects.hash(keyStore, keyAlias);
+        result = 31 * result + Arrays.hashCode(keyStorePassword);
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "PlcCertificateAuthentication{" +
+            "keyStore=" + keyStore.getType() +
+            ", keyAlias='" + (keyAlias != null ? keyAlias : "(first entry)") + 
'\'' +
+            '}';
+    }
+
+}
diff --git 
a/plc4j/api/src/test/java/org/apache/plc4x/java/api/authentication/PlcCertificateAuthenticationTest.java
 
b/plc4j/api/src/test/java/org/apache/plc4x/java/api/authentication/PlcCertificateAuthenticationTest.java
new file mode 100644
index 0000000000..c1d4f07aee
--- /dev/null
+++ 
b/plc4j/api/src/test/java/org/apache/plc4x/java/api/authentication/PlcCertificateAuthenticationTest.java
@@ -0,0 +1,230 @@
+/*
+ * 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
+ *
+ *   https://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.plc4x.java.api.authentication;
+
+import org.junit.jupiter.api.Test;
+
+import java.security.KeyStore;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class PlcCertificateAuthenticationTest {
+
+    @Test
+    void testInstantiation() throws Exception {
+        KeyStore keyStore = createEmptyKeyStore();
+        char[] password = "password".toCharArray();
+
+        PlcCertificateAuthentication auth = new 
PlcCertificateAuthentication(keyStore, password);
+
+        assertNotNull(auth);
+    }
+
+    @Test
+    void testImplementsPlcAuthentication() throws Exception {
+        KeyStore keyStore = createEmptyKeyStore();
+        char[] password = "password".toCharArray();
+
+        PlcCertificateAuthentication auth = new 
PlcCertificateAuthentication(keyStore, password);
+
+        assertTrue(true);
+    }
+
+    @Test
+    void testGetKeyStore() throws Exception {
+        KeyStore keyStore = createEmptyKeyStore();
+        char[] password = "password".toCharArray();
+
+        PlcCertificateAuthentication auth = new 
PlcCertificateAuthentication(keyStore, password);
+
+        assertSame(keyStore, auth.getKeyStore());
+    }
+
+    @Test
+    void testGetKeyStorePassword() throws Exception {
+        KeyStore keyStore = createEmptyKeyStore();
+        char[] password = "password".toCharArray();
+
+        PlcCertificateAuthentication auth = new 
PlcCertificateAuthentication(keyStore, password);
+
+        char[] returnedPassword = auth.getKeyStorePassword();
+        assertArrayEquals(password, returnedPassword);
+        // Verify it returns a copy, not the original
+        assertNotSame(password, returnedPassword);
+    }
+
+    @Test
+    void testGetKeyAliasDefault() throws Exception {
+        KeyStore keyStore = createEmptyKeyStore();
+        char[] password = "password".toCharArray();
+
+        PlcCertificateAuthentication auth = new 
PlcCertificateAuthentication(keyStore, password);
+
+        assertNull(auth.getKeyAlias());
+    }
+
+    @Test
+    void testGetKeyAliasSpecified() throws Exception {
+        KeyStore keyStore = createEmptyKeyStore();
+        char[] password = "password".toCharArray();
+        String alias = "myKey";
+
+        PlcCertificateAuthentication auth = new 
PlcCertificateAuthentication(keyStore, password, alias);
+
+        assertEquals(alias, auth.getKeyAlias());
+    }
+
+    @Test
+    void testConstructorWithNullKeyStoreThrows() {
+        char[] password = "password".toCharArray();
+
+        assertThrows(NullPointerException.class, () ->
+            new PlcCertificateAuthentication(null, password)
+        );
+    }
+
+    @Test
+    void testConstructorWithNullPasswordThrows() throws Exception {
+        KeyStore keyStore = createEmptyKeyStore();
+
+        assertThrows(NullPointerException.class, () ->
+            new PlcCertificateAuthentication(keyStore, null)
+        );
+    }
+
+    @Test
+    void testConstructorWithNullAliasAllowed() throws Exception {
+        KeyStore keyStore = createEmptyKeyStore();
+        char[] password = "password".toCharArray();
+
+        // Null alias should be allowed (means use first entry)
+        PlcCertificateAuthentication auth = new 
PlcCertificateAuthentication(keyStore, password, null);
+
+        assertNull(auth.getKeyAlias());
+    }
+
+    @Test
+    void testPasswordIsCopied() throws Exception {
+        KeyStore keyStore = createEmptyKeyStore();
+        char[] password = "password".toCharArray();
+
+        PlcCertificateAuthentication auth = new 
PlcCertificateAuthentication(keyStore, password);
+
+        // Modify original password
+        password[0] = 'X';
+
+        // Returned password should not be affected
+        char[] returnedPassword = auth.getKeyStorePassword();
+        assertEquals('p', returnedPassword[0]);
+    }
+
+    @Test
+    void testEquals() throws Exception {
+        KeyStore keyStore1 = createEmptyKeyStore();
+        KeyStore keyStore2 = createEmptyKeyStore();
+        char[] password1 = "password".toCharArray();
+        char[] password2 = "password".toCharArray();
+
+        PlcCertificateAuthentication auth1 = new 
PlcCertificateAuthentication(keyStore1, password1);
+        PlcCertificateAuthentication auth2 = new 
PlcCertificateAuthentication(keyStore1, password2);
+        PlcCertificateAuthentication auth3 = new 
PlcCertificateAuthentication(keyStore2, password1);
+
+        // Same keystore and password
+        assertEquals(auth1, auth2);
+        // Different keystore instance
+        assertNotEquals(auth1, auth3);
+    }
+
+    @Test
+    void testEqualsWithAlias() throws Exception {
+        KeyStore keyStore = createEmptyKeyStore();
+        char[] password = "password".toCharArray();
+
+        PlcCertificateAuthentication auth1 = new 
PlcCertificateAuthentication(keyStore, password, "alias1");
+        PlcCertificateAuthentication auth2 = new 
PlcCertificateAuthentication(keyStore, password, "alias1");
+        PlcCertificateAuthentication auth3 = new 
PlcCertificateAuthentication(keyStore, password, "alias2");
+
+        assertEquals(auth1, auth2);
+        assertNotEquals(auth1, auth3);
+    }
+
+    @Test
+    void testEqualsNull() throws Exception {
+        KeyStore keyStore = createEmptyKeyStore();
+        char[] password = "password".toCharArray();
+
+        PlcCertificateAuthentication auth = new 
PlcCertificateAuthentication(keyStore, password);
+
+        assertNotEquals(null, auth);
+    }
+
+    @Test
+    void testEqualsDifferentType() throws Exception {
+        KeyStore keyStore = createEmptyKeyStore();
+        char[] password = "password".toCharArray();
+
+        PlcCertificateAuthentication auth = new 
PlcCertificateAuthentication(keyStore, password);
+
+        assertNotEquals("not an authentication", auth);
+    }
+
+    @Test
+    void testHashCode() throws Exception {
+        KeyStore keyStore = createEmptyKeyStore();
+        char[] password1 = "password".toCharArray();
+        char[] password2 = "password".toCharArray();
+
+        PlcCertificateAuthentication auth1 = new 
PlcCertificateAuthentication(keyStore, password1);
+        PlcCertificateAuthentication auth2 = new 
PlcCertificateAuthentication(keyStore, password2);
+
+        assertEquals(auth1.hashCode(), auth2.hashCode());
+    }
+
+    @Test
+    void testToString() throws Exception {
+        KeyStore keyStore = KeyStore.getInstance("PKCS12");
+        keyStore.load(null, null);
+        char[] password = "password".toCharArray();
+
+        PlcCertificateAuthentication auth = new 
PlcCertificateAuthentication(keyStore, password, "myAlias");
+
+        String str = auth.toString();
+        assertTrue(str.contains("PlcCertificateAuthentication"));
+        assertTrue(str.contains("PKCS12"));
+        assertTrue(str.contains("myAlias"));
+    }
+
+    @Test
+    void testToStringWithoutAlias() throws Exception {
+        KeyStore keyStore = KeyStore.getInstance("PKCS12");
+        keyStore.load(null, null);
+        char[] password = "password".toCharArray();
+
+        PlcCertificateAuthentication auth = new 
PlcCertificateAuthentication(keyStore, password);
+
+        String str = auth.toString();
+        assertTrue(str.contains("(first entry)"));
+    }
+
+    private KeyStore createEmptyKeyStore() throws Exception {
+        KeyStore keyStore = KeyStore.getInstance("PKCS12");
+        keyStore.load(null, null);
+        return keyStore;
+    }
+}

Reply via email to