Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

package org.eclipse.milo.examples.client;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
Expand All @@ -25,7 +26,6 @@
import org.eclipse.milo.opcua.stack.core.security.DefaultClientCertificateValidator;
import org.eclipse.milo.opcua.stack.core.security.FileBasedTrustListManager;
import org.eclipse.milo.opcua.stack.core.security.MemoryCertificateQuarantine;
import org.eclipse.milo.opcua.stack.core.security.TrustListManager;
import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText;
import org.eclipse.milo.opcua.stack.core.util.EndpointUtil;
import org.slf4j.Logger;
Expand All @@ -44,10 +44,10 @@ public class ClientExampleRunner {

private ExampleServer exampleServer;

private TrustListManager clientTrustListManager;

private final ClientExample clientExample;
private final boolean serverRequired;
private final Path securityTempDir;
private final FileBasedTrustListManager clientTrustListManager;

public ClientExampleRunner(ClientExample clientExample) throws Exception {
this(clientExample, true);
Expand All @@ -62,10 +62,8 @@ public ClientExampleRunner(ClientExample clientExample, boolean serverRequired)
exampleServer = new ExampleServer(port, clientExample::configureServer);
exampleServer.startup().get();
}
}

private OpcUaClient createClient() throws Exception {
Path securityTempDir = Paths.get(System.getProperty("java.io.tmpdir"), "client", "security");
securityTempDir = Paths.get(System.getProperty("java.io.tmpdir"), "client", "security");
Files.createDirectories(securityTempDir);
if (!Files.exists(securityTempDir)) {
throw new Exception("unable to create security dir: " + securityTempDir);
Expand All @@ -76,9 +74,11 @@ private OpcUaClient createClient() throws Exception {
LoggerFactory.getLogger(getClass()).info("security dir: {}", securityTempDir.toAbsolutePath());
LoggerFactory.getLogger(getClass()).info("security pki dir: {}", pkiDir.toAbsolutePath());

KeyStoreLoader loader = new KeyStoreLoader().load(securityTempDir);

clientTrustListManager = FileBasedTrustListManager.createAndInitialize(pkiDir);
}

private OpcUaClient createClient() throws Exception {
KeyStoreLoader loader = new KeyStoreLoader().load(securityTempDir);

var certificateValidator =
new DefaultClientCertificateValidator(
Expand Down Expand Up @@ -154,9 +154,11 @@ public void run() {
if (serverRequired && exampleServer != null) {
exampleServer.shutdown().get();
}
Stack.releaseSharedResources();
} catch (ExecutionException | InterruptedException e) {
logger.error("Error disconnecting: {}", e.getMessage(), e);
} finally {
closeClientTrustListManager();
Stack.releaseSharedResources();
}

try {
Expand All @@ -178,6 +180,7 @@ public void run() {
logger.error("Error getting client: {}", t.getMessage(), t);

future.completeExceptionally(t);
closeClientTrustListManager();

try {
Thread.sleep(1000);
Expand All @@ -193,4 +196,12 @@ public void run() {
e.printStackTrace();
}
}

private void closeClientTrustListManager() {
try {
clientTrustListManager.close();
} catch (IOException e) {
logger.error("Error closing TrustListManager: {}", e.getMessage(), e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import static org.eclipse.milo.opcua.sdk.server.OpcUaServerConfig.USER_TOKEN_POLICY_X509;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
Expand All @@ -25,6 +26,7 @@
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
Expand Down Expand Up @@ -92,6 +94,7 @@ public static void main(String[] args) throws Exception {

private final OpcUaServer server;
private final ExampleNamespace exampleNamespace;
private final FileBasedTrustListManager trustListManager;

public ExampleServer() throws Exception {
this(DEFAULT_TCP_BIND_PORT, builder -> {});
Expand Down Expand Up @@ -131,7 +134,7 @@ public ExampleServer(int tcpBindPort, Consumer<OpcUaServerConfigBuilder> configC
"password"::toCharArray,
alias -> "password".toCharArray()));

var trustListManager = FileBasedTrustListManager.createAndInitialize(pkiDir.toPath());
trustListManager = FileBasedTrustListManager.createAndInitialize(pkiDir.toPath());

var certificateQuarantine =
FileBasedCertificateQuarantine.create(pkiDir.toPath().resolve("rejected").resolve("certs"));
Expand Down Expand Up @@ -305,6 +308,15 @@ public CompletableFuture<OpcUaServer> startup() {
public CompletableFuture<OpcUaServer> shutdown() {
exampleNamespace.shutdown();

return server.shutdown();
return server
.shutdown()
.whenComplete(
(server, ex) -> {
try {
trustListManager.close();
} catch (IOException e) {
throw new CompletionException(e);
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* A {@link TrustListManager} backed by directories containing certificates and CRLs.
*
* <p>{@link #initialize()} starts a background thread that watches the configured directories for
* changes. Each OPC UA application should create one shared instance and call {@link #close()} when
* the application shuts down.
*/
public class FileBasedTrustListManager implements TrustListManager, Closeable {

private static final Logger LOGGER = LoggerFactory.getLogger(FileBasedTrustListManager.class);
Expand Down Expand Up @@ -90,6 +97,11 @@ public FileBasedTrustListManager(
Preconditions.checkArgument(trustedCrlDir.toFile().exists(), "trustedCrlDir does not exist");
}

/**
* Load the configured directories and start watching them for changes.
*
* @throws IOException if the directories cannot be watched.
*/
public void initialize() throws IOException {
watchService = FileSystems.getDefault().newWatchService();

Expand Down Expand Up @@ -525,6 +537,9 @@ private static void deleteCrlFromDir(ByteString thumbprint, Path path) {
* Create and initialize a {@link FileBasedTrustListManager} at the specified {@code baseDir},
* creating directories as necessary.
*
* <p>The returned manager owns a background watcher thread. Each OPC UA application should create
* one shared instance and {@link #close() close it} when the application shuts down.
*
* @param baseDir the base directory to manage the Trust List in.
* @return a new, initialized {@link FileBasedTrustListManager} instance.
* @throws IOException if an error occurs creating directories or initializing.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,26 @@
import org.eclipse.milo.opcua.stack.core.types.builtin.ByteString;
import org.eclipse.milo.opcua.stack.core.types.builtin.DateTime;

public interface TrustListManager {
/**
* Manages the issuer and trusted certificates and CRLs used for certificate validation.
*
* <p>Each OPC UA application should create one shared {@link TrustListManager}. Components that
* receive the manager, such as a certificate validator, borrow it and do not close it.
*
* <p>The application must call {@link #close()} when it shuts down. Implementations that do not
* hold resources may use the default no-op implementation.
*/
public interface TrustListManager extends AutoCloseable {

/**
* Release any resources owned by this {@link TrustListManager}.
*
* <p>The default implementation does nothing.
*
* @throws Exception if an error occurs while releasing resources.
*/
@Override
default void close() throws Exception {}

/**
* Get the list of Issuer CRLs.
Expand Down
Loading