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;
+ }
+}