UAServer.java
package de.dlr.bt.stc.opcuaserver;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyPair;
import java.security.Security;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.eclipse.milo.opcua.sdk.server.OpcUaServer;
import org.eclipse.milo.opcua.sdk.server.api.config.OpcUaServerConfig;
import org.eclipse.milo.opcua.sdk.server.identity.CompositeValidator;
import org.eclipse.milo.opcua.sdk.server.identity.UsernameIdentityValidator;
import org.eclipse.milo.opcua.sdk.server.identity.X509IdentityValidator;
import org.eclipse.milo.opcua.sdk.server.util.HostnameUtil;
import org.eclipse.milo.opcua.stack.core.StatusCodes;
import org.eclipse.milo.opcua.stack.core.UaRuntimeException;
import org.eclipse.milo.opcua.stack.core.security.DefaultCertificateManager;
import org.eclipse.milo.opcua.stack.core.security.DefaultTrustListManager;
import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy;
import org.eclipse.milo.opcua.stack.core.transport.TransportProfile;
import org.eclipse.milo.opcua.stack.core.types.builtin.DateTime;
import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText;
import org.eclipse.milo.opcua.stack.core.types.enumerated.MessageSecurityMode;
import org.eclipse.milo.opcua.stack.core.types.structured.BuildInfo;
import org.eclipse.milo.opcua.stack.core.util.CertificateUtil;
import org.eclipse.milo.opcua.stack.core.util.NonceUtil;
import org.eclipse.milo.opcua.stack.core.util.SelfSignedCertificateGenerator;
import org.eclipse.milo.opcua.stack.core.util.SelfSignedHttpsCertificateBuilder;
import org.eclipse.milo.opcua.stack.server.EndpointConfiguration;
import org.greenrobot.eventbus.EventBus;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class UAServer {
private static final String IP_ANY = "0.0.0.0";
static {
// Required for SecurityPolicy.Aes256_Sha256_RsaPss
Security.addProvider(new BouncyCastleProvider());
try {
NonceUtil.blockUntilSecureRandomSeeded(10, TimeUnit.SECONDS);
} catch (ExecutionException | InterruptedException | TimeoutException e) {
e.printStackTrace();
System.exit(-1);
}
}
private final OpcUaServer server;
public UAServer(int tcpBindPort, EventBus eventBus) throws Exception {
Path securityTempDir = Paths.get(System.getProperty("java.io.tmpdir"), "server", "security");
Files.createDirectories(securityTempDir);
if (!Files.exists(securityTempDir)) {
throw new Exception("unable to create security temp dir: " + securityTempDir);
}
File pkiDir = securityTempDir.resolve("pki").toFile();
log.info("security dir: {}", securityTempDir.toAbsolutePath());
log.info("security pki dir: {}", pkiDir.getAbsolutePath());
KeyStoreLoader loader = new KeyStoreLoader().load(securityTempDir);
DefaultCertificateManager certificateManager = new DefaultCertificateManager(loader.getServerKeyPair(),
loader.getServerCertificateChain());
DefaultTrustListManager trustListManager = new DefaultTrustListManager(pkiDir);
// DefaultServerCertificateValidator certificateValidator = new DefaultServerCertificateValidator(
// trustListManager);
// TODO: Think of better way to handle certificates
AcceptAllCertificateValidator acceptAllValidator = new AcceptAllCertificateValidator();
KeyPair httpsKeyPair = SelfSignedCertificateGenerator.generateRsaKeyPair(2048);
SelfSignedHttpsCertificateBuilder httpsCertificateBuilder = new SelfSignedHttpsCertificateBuilder(httpsKeyPair);
httpsCertificateBuilder.setCommonName(HostnameUtil.getHostname());
HostnameUtil.getHostnames(IP_ANY).forEach(httpsCertificateBuilder::addDnsName);
X509Certificate httpsCertificate = httpsCertificateBuilder.build();
// TODO: Implement User access if necessary
UsernameIdentityValidator identityValidator = new UsernameIdentityValidator(true, authChallenge -> true);
X509IdentityValidator x509IdentityValidator = new X509IdentityValidator(c -> true);
// If you need to use multiple certificates you'll have to be smarter than this.
X509Certificate certificate = certificateManager.getCertificates().stream().findFirst()
.orElseThrow(() -> new UaRuntimeException(StatusCodes.Bad_ConfigurationError, "no certificate found"));
// The configured application URI must match the one in the certificate(s)
String applicationUri = CertificateUtil.getSanUri(certificate)
.orElseThrow(() -> new UaRuntimeException(StatusCodes.Bad_ConfigurationError,
"certificate is missing the application URI"));
Set<EndpointConfiguration> endpointConfigurations = createEndpointConfigurations(certificate, tcpBindPort);
@SuppressWarnings("unchecked")
OpcUaServerConfig serverConfig = OpcUaServerConfig.builder().setApplicationUri(applicationUri)
.setApplicationName(LocalizedText.english("Shepard Timeseries Collector"))
.setEndpoints(endpointConfigurations)
.setBuildInfo(new BuildInfo("urn:dlr:stc:server:opcua", "DLR e.V.", "Shepard Timeseries Connector",
OpcUaServer.SDK_VERSION, "", DateTime.now()))
.setCertificateManager(certificateManager).setTrustListManager(trustListManager)
.setCertificateValidator(acceptAllValidator).setHttpsKeyPair(httpsKeyPair)
.setHttpsCertificateChain(new X509Certificate[] { httpsCertificate })
.setIdentityValidator(new CompositeValidator<>(identityValidator, x509IdentityValidator))
.setProductUri("urn:dlr:stc:server:opcua").build();
server = new OpcUaServer(serverConfig);
STCNamespace stcns = new STCNamespace(server, eventBus);
stcns.startup();
}
private Set<EndpointConfiguration> createEndpointConfigurations(X509Certificate certificate, int bindPort) {
Set<EndpointConfiguration> endpointConfigurations = new LinkedHashSet<>();
List<String> bindAddresses = new ArrayList<>();
bindAddresses.add(IP_ANY);
Set<String> hostnames = new LinkedHashSet<>();
hostnames.add(HostnameUtil.getHostname());
hostnames.addAll(HostnameUtil.getHostnames(IP_ANY));
for (String bindAddress : bindAddresses) {
for (String hostname : hostnames) {
EndpointConfiguration.Builder builder = EndpointConfiguration.newBuilder().setBindAddress(bindAddress)
.setHostname(hostname).setPath("/").setCertificate(certificate)
.addTokenPolicies(OpcUaServerConfig.USER_TOKEN_POLICY_ANONYMOUS);
EndpointConfiguration.Builder noSecurityBuilder = builder.copy().setSecurityPolicy(SecurityPolicy.None)
.setSecurityMode(MessageSecurityMode.None);
endpointConfigurations.add(buildTcpEndpoint(noSecurityBuilder, bindPort));
// TCP Basic256Sha256 / SignAndEncrypt
endpointConfigurations
.add(buildTcpEndpoint(builder.copy().setSecurityPolicy(SecurityPolicy.Basic256Sha256)
.setSecurityMode(MessageSecurityMode.SignAndEncrypt), bindPort));
/*
* It's good practice to provide a discovery-specific endpoint with no security.
* It's required practice if all regular endpoints have security configured.
*
* Usage of the "/discovery" suffix is defined by OPC UA Part 6:
*
* Each OPC UA Server Application implements the Discovery Service Set. If the
* OPC UA Server requires a different address for this Endpoint it shall create
* the address by appending the path "/discovery" to its base address.
*/
// EndpointConfiguration.Builder discoveryBuilder = builder.copy().setPath("/discovery")
// .setSecurityPolicy(SecurityPolicy.None).setSecurityMode(MessageSecurityMode.None);
//
// endpointConfigurations.add(buildTcpEndpoint(discoveryBuilder));
}
}
return endpointConfigurations;
}
private static EndpointConfiguration buildTcpEndpoint(EndpointConfiguration.Builder base, int bindPort) {
return base.copy().setTransportProfile(TransportProfile.TCP_UASC_UABINARY).setBindPort(bindPort).build();
}
public OpcUaServer getServer() {
return server;
}
public CompletableFuture<OpcUaServer> startup() {
return server.startup();
}
public CompletableFuture<OpcUaServer> shutdown() {
return server.shutdown();
}
// public List<MultikinematikNamespace> getNamespaces() {
// return Collections.unmodifiableList(namespaces);
// }
}