use of org.eclipse.milo.opcua.stack.core.security.SecurityPolicy in project milo by eclipse.
the class SessionManager method createSession.
private CreateSessionResponse createSession(ServiceRequest serviceRequest) throws UaException {
CreateSessionRequest request = (CreateSessionRequest) serviceRequest.getRequest();
long maxSessionCount = server.getConfig().getLimits().getMaxSessionCount().longValue();
if (createdSessions.size() + activeSessions.size() >= maxSessionCount) {
throw new UaException(StatusCodes.Bad_TooManySessions);
}
ByteString serverNonce = NonceUtil.generateNonce(32);
NodeId authenticationToken = new NodeId(0, NonceUtil.generateNonce(32));
long maxRequestMessageSize = serviceRequest.getServer().getConfig().getEncodingLimits().getMaxMessageSize();
double revisedSessionTimeout = Math.max(5000, Math.min(server.getConfig().getLimits().getMaxSessionTimeout(), request.getRequestedSessionTimeout()));
ApplicationDescription clientDescription = request.getClientDescription();
long secureChannelId = serviceRequest.getSecureChannelId();
EndpointDescription endpoint = serviceRequest.getEndpoint();
SecurityPolicy securityPolicy = SecurityPolicy.fromUri(endpoint.getSecurityPolicyUri());
EndpointDescription[] serverEndpoints = server.getEndpointDescriptions().stream().filter(ed -> !ed.getEndpointUrl().endsWith("/discovery")).filter(ed -> endpointMatchesUrl(ed, request.getEndpointUrl())).filter(ed -> Objects.equal(endpoint.getTransportProfileUri(), ed.getTransportProfileUri())).map(SessionManager::stripNonEssentialFields).toArray(EndpointDescription[]::new);
if (serverEndpoints.length == 0) {
// GetEndpoints in UaStackServer returns *all* endpoints regardless of a hostname
// match in the endpoint URL if the result after filtering is 0 endpoints. Do the
// same here.
serverEndpoints = server.getEndpointDescriptions().stream().filter(ed -> !ed.getEndpointUrl().endsWith("/discovery")).filter(ed -> Objects.equal(endpoint.getTransportProfileUri(), ed.getTransportProfileUri())).map(SessionManager::stripNonEssentialFields).toArray(EndpointDescription[]::new);
}
ByteString clientNonce = request.getClientNonce();
if (securityPolicy != SecurityPolicy.None) {
NonceUtil.validateNonce(clientNonce);
if (clientNonces.contains(clientNonce)) {
throw new UaException(StatusCodes.Bad_NonceInvalid);
}
}
if (securityPolicy != SecurityPolicy.None && clientNonce.isNotNull()) {
clientNonces.add(clientNonce);
while (clientNonces.size() > 64) {
clientNonces.remove(0);
}
}
ByteString clientCertificateBytes = request.getClientCertificate();
if (securityPolicy != SecurityPolicy.None && serviceRequest.getClientCertificateBytes() != null) {
if (!Objects.equal(clientCertificateBytes, serviceRequest.getClientCertificateBytes())) {
throw new UaException(StatusCodes.Bad_SecurityChecksFailed, "certificate used to open secure channel " + "differs from certificate used to create session");
}
}
SecurityConfiguration securityConfiguration = createSecurityConfiguration(endpoint, clientCertificateBytes);
if (securityPolicy != SecurityPolicy.None) {
X509Certificate clientCertificate = securityConfiguration.getClientCertificate();
List<X509Certificate> clientCertificateChain = securityConfiguration.getClientCertificateChain();
if (clientCertificate == null || clientCertificateChain == null) {
throw new UaException(StatusCodes.Bad_SecurityChecksFailed, "client certificate must be non-null");
}
ServerCertificateValidator certificateValidator = server.getConfig().getCertificateValidator();
certificateValidator.validateCertificateChain(clientCertificateChain, clientDescription.getApplicationUri());
}
// SignatureData must be created using only the bytes of the client
// leaf certificate, not the bytes of the client certificate chain.
SignatureData serverSignature = getServerSignature(securityPolicy, securityConfiguration.getKeyPair(), clientNonce, securityConfiguration.getClientCertificateBytes());
NodeId sessionId = new NodeId(1, "Session:" + UUID.randomUUID());
String sessionName = request.getSessionName();
Duration sessionTimeout = Duration.ofMillis(DoubleMath.roundToLong(revisedSessionTimeout, RoundingMode.UP));
Session session = new Session(server, sessionId, sessionName, sessionTimeout, clientDescription, request.getServerUri(), request.getMaxResponseMessageSize(), endpoint, secureChannelId, securityConfiguration);
session.setLastNonce(serverNonce);
session.addLifecycleListener((s, remove) -> {
createdSessions.remove(authenticationToken);
activeSessions.remove(authenticationToken);
sessionListeners.forEach(l -> l.onSessionClosed(s));
});
createdSessions.put(authenticationToken, session);
sessionListeners.forEach(l -> l.onSessionCreated(session));
return new CreateSessionResponse(serviceRequest.createResponseHeader(), sessionId, authenticationToken, revisedSessionTimeout, serverNonce, endpoint.getServerCertificate(), serverEndpoints, new SignedSoftwareCertificate[0], serverSignature, uint(maxRequestMessageSize));
}
use of org.eclipse.milo.opcua.stack.core.security.SecurityPolicy in project vantiq-extension-sources by Vantiq.
the class OpcUaESClient method createClient.
@SuppressWarnings({ "PMD.CognitiveComplexity", "PMD.MethodLengthCheck" })
private OpcUaClient createClient(Map<String, Object> config) throws Exception {
if (storageDirectory == null) {
throw new OpcExtConfigException(ERROR_PREFIX + ".missingStorageDirectory: No storage directory specified.");
}
SecurityPolicy securityPolicy = determineSecurityPolicy(config);
MessageSecurityMode msgSecMode = determineMessageSecurityMode(config);
File securityDir = new File(storageDirectory, SECURITY_DIRECTORY);
if (!securityDir.exists() && !securityDir.mkdirs()) {
throw new OpcExtConfigException(ERROR_PREFIX + ".invalidStorageDirectory: unable to create security dir: " + securityDir);
}
log.info("security temp dir: {}", securityDir.getAbsolutePath());
keyStoreManager = new KeyStoreManager().load(securityDir);
IdentityProvider idProvider = constructIdentityProvider(config);
List<EndpointDescription> endpoints;
discoveryEndpoint = (String) config.get(OpcConstants.CONFIG_DISCOVERY_ENDPOINT);
serverEndpoint = (String) config.get(OpcConstants.CONFIG_SERVER_ENDPOINT);
if (discoveryEndpoint == null && serverEndpoint == null) {
String errorMsg = ERROR_PREFIX + ".noDiscoveryEndpoint: No discovery or server endpoint was provided in the configuration.";
log.error(errorMsg);
throw new OpcExtConfigException(errorMsg);
}
OpcUaClientConfig opcConfig;
try {
endpoints = DiscoveryClient.getEndpoints(discoveryEndpoint).get();
} catch (Throwable ex) {
try {
// try the explicit discovery endpoint as well
String discoveryUrl = discoveryEndpoint + "/discovery";
log.info("Trying explicit discovery URL: {}", discoveryUrl);
endpoints = DiscoveryClient.getEndpoints(discoveryUrl).get();
} catch (ExecutionException e) {
String errMsg = ERROR_PREFIX + ".discoveryError: Could not discover OPC Endpoints:" + e.getClass().getName() + "::" + e.getMessage();
log.error(ERROR_PREFIX + ".discoveryError: Could not discover OPC Endpoints: {} :: {}", e.getClass().getName(), e.getMessage());
throw new OpcExtConfigException(errMsg, e);
}
}
if (log.isDebugEnabled()) {
logDiscoveredEndpoints(endpoints);
}
List<EndpointDescription> validEndpoints = endpoints.stream().filter(e -> (e.getSecurityPolicyUri().equals(securityPolicy.getUri()) && e.getSecurityMode().equals(msgSecMode))).collect(Collectors.toList());
if (log.isDebugEnabled()) {
logAcceptableEndpoints(validEndpoints, securityPolicy, msgSecMode);
// The following code is here for testing only. It allows us to fake a poorly configured
// server that reports invalid or unreachable endpoints as part of discovery. This is, for
// reasons I'm sure i don't agree with, part of the protocol, so we must tolerate it. The
// purportedly proper response is to substitute the address used for discovery for any
// unreachable addresses. This, of course, makes little sense since the whole point of discovery
// is to allow these to be spread across different nodes. But I didn't write the spec.
Boolean fakeBadAddress = (Boolean) config.get(OpcConstants.CONFIG_TEST_DISCOVERY_UNREACHABLE);
if (fakeBadAddress != null && fakeBadAddress) {
List<EndpointDescription> newValidEndpoints = new ArrayList<>();
for (EndpointDescription e : validEndpoints) {
URI url = new URI(e.getEndpointUrl());
URI borkedUri = new URI(url.getScheme(), null, "utterlyWorthlessHostThatShouldNeverResolve", url.getPort(), url.getPath(), null, null);
EndpointDescription borkedEd = new EndpointDescription(borkedUri.toString(), e.getServer(), e.getServerCertificate(), e.getSecurityMode(), e.getSecurityPolicyUri(), e.getUserIdentityTokens(), e.getTransportProfileUri(), e.getSecurityLevel());
newValidEndpoints.add(borkedEd);
}
validEndpoints = newValidEndpoints;
}
}
// First, we'll look for an endpoint that doesn't contain localhost. This is, generally,
// a not too useful configuration since localhost is always a relative address.
EndpointDescription endpoint = validEndpoints.stream().filter(e -> {
try {
// Note: Must use URI here. If you use URL, it will fail with
// a MailformedURLException because the generic system doesn't
// understand opc.tcp: as a scheme/protocol.
URI url = new URI(e.getEndpointUrl());
InetAddress ina = InetAddress.getByName(url.getHost());
if (!ina.isLoopbackAddress() || ina.isReachable(3000)) {
return true;
}
} catch (UnknownHostException | URISyntaxException ex) {
log.warn("Recoverable error during discovered server URL validation:" + ex.getClass().getName() + "::" + ex.getMessage() + "-->" + e.getEndpointUrl());
} catch (Exception ex) {
// This means that we have some non-optimal addresses returned by discovery.
// In these cases, we'll leave it up to the SDK & network stack to figure out how to get there.
log.debug("Recoverable error during discovered server URL validation. Left to network stack to resolve:" + ex.getClass().getName() + "::" + ex.getMessage() + "-->" + e.getEndpointUrl());
}
return false;
}).findFirst().orElse(null);
if (endpoint == null) {
// Discovery server returned either no reasonable endpoints or none that weren't a loopback.
log.warn("No servers at reachable, non-loopback addresses found via discovery. " + "Fixing up addresses to match discovery server.");
endpoint = validEndpoints.stream().findFirst().orElse(null);
if (endpoint != null) {
endpoint = fixLookbackAddress(endpoint);
}
}
if (endpoint == null) {
throw new Exception("No acceptable endpoints returned for security policy: " + securityPolicy.getUri() + " and security mode " + msgSecMode);
}
if (serverEndpoint != null) {
// Then we'll override the endpoint provided but otherwise use the endpoint descriptor returned.
// The SDK seems to have an issue when no EndpointDescriptor is provided.
EndpointDescription newEndpoint = new EndpointDescription(serverEndpoint, endpoint.getServer(), endpoint.getServerCertificate(), endpoint.getSecurityMode(), endpoint.getSecurityPolicyUri(), endpoint.getUserIdentityTokens(), endpoint.getTransportProfileUri(), endpoint.getSecurityLevel());
log.debug("Replacing endpoint address with provided serverEndpoint: {} --> {}", endpoint.getEndpointUrl(), newEndpoint.getEndpointUrl());
endpoint = newEndpoint;
}
log.info("Using discovered endpoint: {} [{}, {}]", endpoint.getEndpointUrl(), securityPolicy, msgSecMode.toString());
opcConfig = OpcUaClientConfig.builder().setApplicationName(LocalizedText.english("VANTIQ OPC-UA Source")).setApplicationUri("urn:io:vantiq:extsrc:opcua:client").setCertificate(keyStoreManager.getClientCertificate()).setKeyPair(keyStoreManager.getClientKeyPair()).setEndpoint(endpoint).setIdentityProvider(idProvider).setRequestTimeout(uint(5000)).build();
return OpcUaClient.create(opcConfig);
}
use of org.eclipse.milo.opcua.stack.core.security.SecurityPolicy in project vantiq-extension-sources by Vantiq.
the class OpcUaESClient method determineMessageSecurityMode.
private MessageSecurityMode determineMessageSecurityMode(Map<String, Object> config) throws OpcExtConfigException {
// config.messageSecurityMode should be the URI for the appropriate security policy.
String msgSecModeSpec = (String) config.get(OpcConstants.CONFIG_MESSAGE_SECURITY_MODE);
MessageSecurityMode msgSecMode;
if (msgSecModeSpec == null || msgSecModeSpec.isEmpty()) {
// No message security mode will default to either #NONE or #SignAndEncrypt, depending on the security policy. We will, however, log a warning\
SecurityPolicy secPol = determineSecurityPolicy(config);
if (secPol.equals(SecurityPolicy.None)) {
msgSecModeSpec = MessageSecurityMode.None.toString();
} else {
msgSecModeSpec = MessageSecurityMode.SignAndEncrypt.toString();
}
log.warn(ERROR_PREFIX + ".defaultMessageSecurityMode: No OPC UA message security mode was specified in the configuration. " + "Using default value of '{}' based on the securityPolicy value of '{}'", msgSecModeSpec, secPol.getUri());
}
try {
msgSecMode = MessageSecurityMode.valueOf(msgSecModeSpec);
return msgSecMode;
} catch (IllegalArgumentException e) {
String errMsg = ERROR_PREFIX + ".invalidMessageSecurityMode: " + msgSecModeSpec + " is not a valid message security mode.";
log.error(errMsg);
throw new OpcExtConfigException(errMsg, e);
}
}
use of org.eclipse.milo.opcua.stack.core.security.SecurityPolicy in project vantiq-extension-sources by Vantiq.
the class Connection method testConnectionSecureUpw.
@Test
public void testConnectionSecureUpw() throws Exception {
List<EndpointDescription> eps = exampleServer.getServer().getEndpointDescriptions();
EnumSet<MessageSecurityMode> serverMsgModes = EnumSet.noneOf(MessageSecurityMode.class);
EnumSet<SecurityPolicy> serverSecPols = EnumSet.noneOf(SecurityPolicy.class);
for (EndpointDescription ep : eps) {
if (ep.getEndpointUrl().startsWith("opc.tpc")) {
// At present, these are all we test
serverSecPols.add(SecurityPolicy.fromUri(ep.getSecurityPolicyUri()));
serverMsgModes.add(ep.getSecurityMode());
}
}
log.debug("For example server found secPols: {}, msgSec: {}", serverSecPols, serverMsgModes);
// Below, we'll traverse the valid combinations. None's must be paired and are tested elsewhere
for (SecurityPolicy secPol : serverSecPols) {
if (!secPol.equals(SecurityPolicy.None)) {
for (MessageSecurityMode msgSec : serverMsgModes) {
if (!msgSec.equals(MessageSecurityMode.None)) {
log.info("Attempting sync connection using [{}, {}]", secPol, msgSec);
makeConnection(false, secPol.getUri(), msgSec.toString(), true);
log.info("Attempting sync connection using [{}, {}]", secPol, "(missing)");
makeConnection(false, secPol.getUri(), // Also check that the defaulting works correctly
null, true);
log.info("Attempting async connection using [{}, {}]", secPol, msgSec);
makeConnection(true, secPol.getUri(), msgSec.toString(), true);
log.info("Attempting async connection using [{}, {}] with explicit anonymous user", secPol, msgSec);
makeConnection(true, secPol.getUri(), msgSec.toString(), OpcConstants.CONFIG_IDENTITY_ANONYMOUS, null, true);
// Valid user/pw combos from ExampleServer:
// "user, password1" & "admin, password2"
String[] upwCombos = { "user, password1", "admin,password2", "user, password1" };
for (String uPw : upwCombos) {
log.info("Attempting sync connection using [{}, {}] using username/password: '{}'", secPol, msgSec, uPw);
makeConnection(false, secPol.getUri(), msgSec.toString(), OpcConstants.CONFIG_IDENTITY_USERNAME_PASSWORD, uPw, true);
}
for (String uPw : upwCombos) {
log.info("Attempting async connection using [{}, {}] using username/password: '{}'", secPol, msgSec, uPw);
makeConnection(true, secPol.getUri(), msgSec.toString(), OpcConstants.CONFIG_IDENTITY_USERNAME_PASSWORD, uPw, true);
}
}
}
}
}
}
use of org.eclipse.milo.opcua.stack.core.security.SecurityPolicy in project vantiq-extension-sources by Vantiq.
the class Connection method testConnectionSecureCert.
@Test
public void testConnectionSecureCert() throws Exception {
List<EndpointDescription> eps = exampleServer.getServer().getEndpointDescriptions();
EnumSet<MessageSecurityMode> serverMsgModes = EnumSet.noneOf(MessageSecurityMode.class);
EnumSet<SecurityPolicy> serverSecPols = EnumSet.noneOf(SecurityPolicy.class);
for (EndpointDescription ep : eps) {
if (ep.getEndpointUrl().startsWith("opc.tpc")) {
// At present, these are all we test
serverSecPols.add(SecurityPolicy.fromUri(ep.getSecurityPolicyUri()));
serverMsgModes.add(ep.getSecurityMode());
}
}
// Below, we'll traverse the valid combinations. None's must be paired and are tested elsewhere
for (SecurityPolicy secPol : serverSecPols) {
if (!secPol.equals(SecurityPolicy.None)) {
for (MessageSecurityMode msgSec : serverMsgModes) {
if (!msgSec.equals(MessageSecurityMode.None)) {
// Defaults tested in *Upw test...
for (String certKey : trustedTestCerts) {
log.info("Attempting sync connection using [{}, {}] using certificate: '{}'", secPol, msgSec, certKey);
makeConnection(false, secPol.getUri(), msgSec.toString(), OpcConstants.CONFIG_IDENTITY_CERTIFICATE, certKey, true);
log.info("Attempting async connection using [{}, {}] using certificate: '{}'", secPol, msgSec, certKey);
makeConnection(true, secPol.getUri(), msgSec.toString(), OpcConstants.CONFIG_IDENTITY_CERTIFICATE, certKey, true);
}
}
}
}
}
}
Aggregations