Skip to content

Commit

Permalink
fix: Fix remaining unit tests.
Browse files Browse the repository at this point in the history
Signed-off-by: Łukasz Dywicki <[email protected]>
  • Loading branch information
splatch committed Feb 5, 2024
1 parent 6b06755 commit 5e64425
Show file tree
Hide file tree
Showing 3 changed files with 266 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ public class SecureChannel {
private static final int DEFAULT_MAX_MESSAGE_SIZE = 2097152;
private static final int DEFAULT_RECEIVE_BUFFER_SIZE = 65535;
private static final int DEFAULT_SEND_BUFFER_SIZE = 65535;
public static final Duration REQUEST_TIMEOUT = Duration.ofMillis(10000);
public static final long REQUEST_TIMEOUT_LONG = 10000L;
public static final Duration REQUEST_TIMEOUT = Duration.ofMillis(1000000);
public static final long REQUEST_TIMEOUT_LONG = 1000000L;
private static final String PASSWORD_ENCRYPTION_ALGORITHM = "http://www.w3.org/2001/04/xmlenc#rsa-oaep";
private static final PascalString SECURITY_POLICY_NONE = new PascalString("http://opcfoundation.org/UA/SecurityPolicy#None");
protected static final PascalString NULL_STRING = new PascalString("");
Expand Down Expand Up @@ -154,7 +154,7 @@ public SecureChannel(OpcuaDriverContext driverContext, OpcuaConfiguration config
this.securityPolicy = determineSecurityPolicy(configuration, driverContext);
CertificateKeyPair ckp = driverContext.getCertificateKeyPair();

if (this.securityPolicy == SecurityPolicy.Basic256Sha256) {
if (this.securityPolicy != SecurityPolicy.NONE) {
//Sender Certificate gets populated during the 'discover' phase when encryption is enabled.
this.senderCertificate = configuration.getSenderCertificate();
this.encryptionHandler = new EncryptionHandler(ckp, this.senderCertificate, configuration.getSecurityPolicy());
Expand Down Expand Up @@ -763,7 +763,7 @@ private void onDisconnectCloseSecureChannel(ConversationContext<OpcuaAPU> contex
public void onDiscover(ConversationContext<OpcuaAPU> context) {
if (!driverContext.getEncrypted()) {
LOGGER.debug("not encrypted, ignoring onDiscover");
context.fireDiscovered(configuration);
context.fireDiscovered(this.configuration);
return;
}
// Only the TCP transport supports login.
Expand Down Expand Up @@ -953,12 +953,24 @@ public void onDiscoverGetEndpointsRequest(ConversationContext<OpcuaAPU> context,
List<ExtensionObjectDefinition> endpoints = response.getEndpoints();
for (ExtensionObjectDefinition endpoint : endpoints) {
EndpointDescription endpointDescription = (EndpointDescription) endpoint;
if (endpointDescription.getEndpointUrl().getStringValue().equals(this.endpoint.getStringValue())
&& endpointDescription.getSecurityPolicyUri().getStringValue().equals(this.securityPolicy.getSecurityPolicyUri())) {

boolean urlMatch = endpointDescription.getEndpointUrl().getStringValue().equals(this.endpoint.getStringValue());
boolean policyMatch = endpointDescription.getSecurityPolicyUri().getStringValue().equals(this.securityPolicy.getSecurityPolicyUri());

LOGGER.debug("Validate OPC UA endpoint {} during discovery phase."
+ "Expected {}. Endpoint policy {} looking for {}", endpointDescription.getEndpointUrl().getStringValue(), this.endpoint.getStringValue(),
endpointDescription.getSecurityPolicyUri().getStringValue(), securityPolicy.getSecurityPolicyUri());

if (urlMatch && policyMatch) {
LOGGER.info("Found OPC UA endpoint {}", this.endpoint.getStringValue());
configuration.setSenderCertificate(endpointDescription.getServerCertificate().getStringValue());
break;
}
}
}

if (configuration.getSenderCertificate() == null) {
throw new IllegalArgumentException("");
}

try {
MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,9 @@
*/
package org.apache.plc4x.java.opcua;

import io.vavr.Tuple2;
import io.vavr.collection.List;
import java.util.ArrayList;
import java.util.concurrent.CompletableFuture;
import java.util.Map;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
Expand All @@ -30,7 +29,6 @@
import org.apache.plc4x.java.api.PlcConnectionManager;
import org.apache.plc4x.java.api.PlcDriverManager;
import org.apache.plc4x.java.api.authentication.PlcUsernamePasswordAuthentication;
import org.apache.plc4x.java.api.exceptions.PlcConnectionException;
import org.apache.plc4x.java.api.messages.PlcReadRequest;
import org.apache.plc4x.java.api.messages.PlcReadResponse;
import org.apache.plc4x.java.api.messages.PlcSubscriptionEvent;
Expand All @@ -42,7 +40,7 @@
import org.apache.plc4x.java.opcua.security.SecurityPolicy;
import org.apache.plc4x.java.opcua.tag.OpcuaTag;
import org.assertj.core.api.Condition;
import org.eclipse.milo.examples.server.ExampleServer;
import org.eclipse.milo.examples.server.TestMiloServer;
import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
Expand All @@ -56,9 +54,7 @@
import java.util.concurrent.ExecutionException;
import java.util.stream.Stream;

import static java.util.concurrent.Executors.newSingleThreadExecutor;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;

public class OpcuaPlcDriverTest {

Expand Down Expand Up @@ -134,7 +130,7 @@ public class OpcuaPlcDriverTest {
final List<String> discoveryParamValidSet = List.of(discoveryValidParamTrue, discoveryValidParamFalse);
List<String> discoveryParamCorruptedSet = List.of(discoveryCorruptedParamWrongValueNum, discoveryCorruptedParamWrongName);

private static ExampleServer exampleServer;
private static TestMiloServer exampleServer;

@BeforeAll
public static void setup() throws Exception {
Expand All @@ -148,7 +144,7 @@ public static void setup() throws Exception {
// Ignore this ...
}

exampleServer = new ExampleServer();
exampleServer = new TestMiloServer();
exampleServer.startup().get();
}

Expand Down Expand Up @@ -564,20 +560,18 @@ private String getConnectionString(SecurityPolicy policy) {
case NONE:
return tcpConnectionAddress;
case Basic128Rsa15:
fail("Unsupported");
return null;
case Basic256Sha256:
Path securityTempDir = Paths.get(System.getProperty("java.io.tmpdir"), "server");
String keyStoreFile = securityTempDir.resolve("security").resolve("example-server.pfx").toAbsolutePath().toString();

String certDirectory = securityTempDir.toAbsolutePath().toString();
String connectionParams = Stream.of(
new Tuple2<>("keyStoreFile", keyStoreFile),
new Tuple2<>("certDirectory", certDirectory),
new Tuple2<>("keyStorePassword", "password"),
new Tuple2<>("securityPolicy", policy)
Map.entry("keyStoreFile", keyStoreFile),
Map.entry("certDirectory", certDirectory),
Map.entry("keyStorePassword", "password"),
Map.entry("securityPolicy", policy)
)
.map(tuple -> tuple._1() + "=" + tuple._2())
.map(tuple -> tuple.getKey() + "=" + tuple.getValue())
.collect(Collectors.joining(paramDivider));


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
/*
* 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.eclipse.milo.examples.server;

import static com.google.common.collect.Lists.newArrayList;
import static org.eclipse.milo.opcua.sdk.server.api.config.OpcUaServerConfig.USER_TOKEN_POLICY_ANONYMOUS;
import static org.eclipse.milo.opcua.sdk.server.api.config.OpcUaServerConfig.USER_TOKEN_POLICY_USERNAME;
import static org.eclipse.milo.opcua.sdk.server.api.config.OpcUaServerConfig.USER_TOKEN_POLICY_X509;

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.cert.X509Certificate;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
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.SelfSignedCertificateGenerator;
import org.eclipse.milo.opcua.stack.core.util.SelfSignedHttpsCertificateBuilder;
import org.eclipse.milo.opcua.stack.server.EndpointConfiguration;
import org.eclipse.milo.opcua.stack.server.security.DefaultServerCertificateValidator;
import org.slf4j.LoggerFactory;

/**
* A test server implementation which.
*/
public class TestMiloServer {

private static final int TCP_BIND_PORT = 12686;
private static final int HTTPS_BIND_PORT = 8443;

private final OpcUaServer server;
private final ExampleNamespace exampleNamespace;

public TestMiloServer() 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();

LoggerFactory.getLogger(getClass())
.info("security dir: {}", securityTempDir.toAbsolutePath());
LoggerFactory.getLogger(getClass())
.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);
trustListManager.setTrustedCertificates(new ArrayList<>(certificateManager.getCertificates()));

DefaultServerCertificateValidator certificateValidator =
new DefaultServerCertificateValidator(trustListManager);

KeyPair httpsKeyPair = SelfSignedCertificateGenerator.generateRsaKeyPair(2048);

SelfSignedHttpsCertificateBuilder httpsCertificateBuilder = new SelfSignedHttpsCertificateBuilder(httpsKeyPair);
httpsCertificateBuilder.setCommonName(HostnameUtil.getHostname());
HostnameUtil.getHostnames("0.0.0.0").forEach(httpsCertificateBuilder::addDnsName);
X509Certificate httpsCertificate = httpsCertificateBuilder.build();

UsernameIdentityValidator identityValidator = new UsernameIdentityValidator(
true,
authChallenge -> {
String username = authChallenge.getUsername();
String password = authChallenge.getPassword();

boolean userOk = "user".equals(username) && "password1".equals(password);
boolean adminOk = "admin".equals(username) && "password2".equals(password);

return userOk || adminOk;
}
);

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);

OpcUaServerConfig serverConfig = OpcUaServerConfig.builder()
.setApplicationUri(applicationUri)
.setApplicationName(LocalizedText.english("Eclipse Milo OPC UA Example Server"))
.setEndpoints(endpointConfigurations)
.setBuildInfo(
new BuildInfo(
"urn:eclipse:milo:example-server",
"eclipse",
"eclipse milo example server",
OpcUaServer.SDK_VERSION,
"", DateTime.now()))
.setCertificateManager(certificateManager)
.setTrustListManager(trustListManager)
.setCertificateValidator(certificateValidator)
.setHttpsKeyPair(httpsKeyPair)
.setHttpsCertificateChain(new X509Certificate[]{httpsCertificate})
.setIdentityValidator(new CompositeValidator(identityValidator, x509IdentityValidator))
.setProductUri("urn:eclipse:milo:example-server")
.build();

server = new OpcUaServer(serverConfig);

exampleNamespace = new ExampleNamespace(server);
exampleNamespace.startup();
}

private Set<EndpointConfiguration> createEndpointConfigurations(X509Certificate certificate) {
Set<EndpointConfiguration> endpointConfigurations = new LinkedHashSet<>();

List<String> bindAddresses = newArrayList();
bindAddresses.add("0.0.0.0");

Set<String> hostnames = new LinkedHashSet<>();
hostnames.add(HostnameUtil.getHostname());
hostnames.addAll(HostnameUtil.getHostnames("0.0.0.0"));

for (String bindAddress : bindAddresses) {
for (String hostname : hostnames) {
EndpointConfiguration.Builder builder = EndpointConfiguration.newBuilder()
.setBindAddress(bindAddress)
.setHostname(hostname)
.setPath("/milo")
.setCertificate(certificate)
.addTokenPolicies(
USER_TOKEN_POLICY_ANONYMOUS,
USER_TOKEN_POLICY_USERNAME,
USER_TOKEN_POLICY_X509
);


EndpointConfiguration.Builder noSecurityBuilder = builder.copy()
.setSecurityPolicy(SecurityPolicy.None)
.setSecurityMode(MessageSecurityMode.None);

endpointConfigurations.add(buildTcpEndpoint(noSecurityBuilder));

// TCP Basic256Sha256 / SignAndEncrypt
endpointConfigurations.add(buildTcpEndpoint(
builder.copy()
.setSecurityPolicy(SecurityPolicy.Basic256Sha256)
.setSecurityMode(MessageSecurityMode.SignAndEncrypt))
);
// TCP Basic128Rsa15 / SignAndEncrypt
endpointConfigurations.add(buildTcpEndpoint(
builder.copy()
.setSecurityPolicy(SecurityPolicy.Basic128Rsa15)
.setSecurityMode(MessageSecurityMode.SignAndEncrypt))
);

EndpointConfiguration.Builder discoveryBuilder = builder.copy()
.setPath("/milo/discovery")
.setSecurityPolicy(SecurityPolicy.None)
.setSecurityMode(MessageSecurityMode.None);

endpointConfigurations.add(buildTcpEndpoint(discoveryBuilder));
}
}

return endpointConfigurations;
}

private static EndpointConfiguration buildTcpEndpoint(EndpointConfiguration.Builder base) {
return base.copy()
.setTransportProfile(TransportProfile.TCP_UASC_UABINARY)
.setBindPort(TCP_BIND_PORT)
.build();
}

public OpcUaServer getServer() {
return server;
}

public CompletableFuture<OpcUaServer> startup() {
return server.startup();
}

public CompletableFuture<OpcUaServer> shutdown() {
exampleNamespace.shutdown();

return server.shutdown();
}

}

0 comments on commit 5e64425

Please sign in to comment.