mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-28 14:09:56 +08:00
A new configuration variable
<HttpsMutualTls>true</HttpsMutualTls>
enables the mutual TLS authentication support. The behavior of the TLS endpoint changes as the following:
HttpsMutualTls is true:
The server checks the trust on the certificate which is used by the client for TLS authentication. It must be a valid OPC UA application certificate which is trusted.
A client can still connect without providing a client certificate, but then it is only able to call discovery services.
In order to create a session, the client must use the same application certificate that was used for the TLS channel.
HttpsMutualTls is false:
- There is no application authentication. The server endpoint uses security None and there is no client application authentication.
Instead, only user authentication is used to secure the server, anonymous user authentication is disabled.
Discovery service calls are supported.
Co-authored-by: Martin Regen <mregen@microsoft.com>
324 lines
14 KiB
C#
324 lines
14 KiB
C#
/* ========================================================================
|
|
* Copyright (c) 2005-2020 The OPC Foundation, Inc. All rights reserved.
|
|
*
|
|
* OPC Foundation MIT License 1.00
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person
|
|
* obtaining a copy of this software and associated documentation
|
|
* files (the "Software"), to deal in the Software without
|
|
* restriction, including without limitation the rights to use,
|
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following
|
|
* conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be
|
|
* included in all copies or substantial portions of the Software.
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
* OTHER DEALINGS IN THE SOFTWARE.
|
|
*
|
|
* The complete license agreement can be found here:
|
|
* http://opcfoundation.org/License/MIT/1.00/
|
|
* ======================================================================*/
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Opc.Ua.Security;
|
|
|
|
namespace Opc.Ua.Client
|
|
{
|
|
/// <summary>
|
|
/// Defines numerous re-useable utility functions for clients.
|
|
/// </summary>
|
|
public static partial class CoreClientUtils
|
|
{
|
|
/// <summary>
|
|
/// The default discover operation timeout.
|
|
/// </summary>
|
|
public static readonly int DefaultDiscoverTimeout = 15000;
|
|
|
|
#region Discovery
|
|
/// <summary>
|
|
/// Discovers the servers on the local machine.
|
|
/// </summary>
|
|
/// <param name="configuration">The configuration.</param>
|
|
/// <returns>A list of server urls.</returns>
|
|
public static IList<string> DiscoverServers(ApplicationConfiguration configuration)
|
|
{
|
|
return DiscoverServers(configuration, DefaultDiscoverTimeout);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Discovers the servers on the local machine.
|
|
/// </summary>
|
|
/// <param name="configuration">The configuration.</param>
|
|
/// <param name="discoverTimeout">Operation timeout in milliseconds.</param>
|
|
/// <returns>A list of server urls.</returns>
|
|
public static IList<string> DiscoverServers(
|
|
ApplicationConfiguration configuration,
|
|
int discoverTimeout
|
|
)
|
|
{
|
|
List<string> serverUrls = new List<string>();
|
|
|
|
// set a short timeout because this is happening in the drop down event.
|
|
var endpointConfiguration = EndpointConfiguration.Create(configuration);
|
|
endpointConfiguration.OperationTimeout = discoverTimeout;
|
|
|
|
// Connect to the local discovery server and find the available servers.
|
|
using (DiscoveryClient client = DiscoveryClient.Create(new Uri(Utils.Format(Utils.DiscoveryUrls[0], "localhost")), endpointConfiguration))
|
|
{
|
|
ApplicationDescriptionCollection servers = client.FindServers(null);
|
|
|
|
// populate the drop down list with the discovery URLs for the available servers.
|
|
for (int ii = 0; ii < servers.Count; ii++)
|
|
{
|
|
if (servers[ii].ApplicationType == ApplicationType.DiscoveryServer)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for (int jj = 0; jj < servers[ii].DiscoveryUrls.Count; jj++)
|
|
{
|
|
string discoveryUrl = servers[ii].DiscoveryUrls[jj];
|
|
|
|
// Many servers will use the '/discovery' suffix for the discovery endpoint.
|
|
// The URL without this prefix should be the base URL for the server.
|
|
if (discoveryUrl.EndsWith(ConfiguredEndpoint.DiscoverySuffix, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
discoveryUrl = discoveryUrl.Substring(0, discoveryUrl.Length - ConfiguredEndpoint.DiscoverySuffix.Length);
|
|
}
|
|
|
|
// ensure duplicates do not get added.
|
|
if (!serverUrls.Exists(serverUrl => serverUrl.Equals(discoveryUrl, StringComparison.OrdinalIgnoreCase)))
|
|
{
|
|
serverUrls.Add(discoveryUrl);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return serverUrls;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Finds the endpoint that best matches the current settings.
|
|
/// </summary>
|
|
/// <param name="discoveryUrl">The discovery URL.</param>
|
|
/// <param name="useSecurity">if set to <c>true</c> select an endpoint that uses security.</param>
|
|
/// <returns>The best available endpoint.</returns>
|
|
public static EndpointDescription SelectEndpoint(string discoveryUrl, bool useSecurity)
|
|
{
|
|
return SelectEndpoint(discoveryUrl, useSecurity, DefaultDiscoverTimeout);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Finds the endpoint that best matches the current settings.
|
|
/// </summary>
|
|
/// <param name="discoveryUrl">The discovery URL.</param>
|
|
/// <param name="useSecurity">if set to <c>true</c> select an endpoint that uses security.</param>
|
|
/// <param name="discoverTimeout">Operation timeout in milliseconds.</param>
|
|
/// <returns>The best available endpoint.</returns>
|
|
public static EndpointDescription SelectEndpoint(
|
|
string discoveryUrl,
|
|
bool useSecurity,
|
|
int discoverTimeout
|
|
)
|
|
{
|
|
var url = GetDiscoveryUrl(discoveryUrl);
|
|
var endpointConfiguration = EndpointConfiguration.Create();
|
|
endpointConfiguration.OperationTimeout = discoverTimeout;
|
|
|
|
// Connect to the server's discovery endpoint and find the available configuration.
|
|
using (var client = DiscoveryClient.Create(url, endpointConfiguration))
|
|
{
|
|
var endpoints = client.GetEndpoints(null);
|
|
return SelectEndpoint(url, endpoints, useSecurity);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Finds the endpoint that best matches the current settings.
|
|
/// </summary>
|
|
public static EndpointDescription SelectEndpoint(
|
|
ApplicationConfiguration application,
|
|
ITransportWaitingConnection connection,
|
|
bool useSecurity
|
|
)
|
|
{
|
|
return SelectEndpoint(application, connection, useSecurity, DefaultDiscoverTimeout);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Finds the endpoint that best matches the current settings.
|
|
/// </summary>
|
|
public static EndpointDescription SelectEndpoint(
|
|
ApplicationConfiguration application,
|
|
ITransportWaitingConnection connection,
|
|
bool useSecurity,
|
|
int discoverTimeout
|
|
)
|
|
{
|
|
var endpointConfiguration = EndpointConfiguration.Create();
|
|
endpointConfiguration.OperationTimeout = discoverTimeout > 0 ? discoverTimeout : DefaultDiscoverTimeout;
|
|
|
|
using (DiscoveryClient client = DiscoveryClient.Create(application, connection, endpointConfiguration))
|
|
{
|
|
var url = new Uri(client.Endpoint.EndpointUrl);
|
|
var endpoints = client.GetEndpoints(null);
|
|
return SelectEndpoint(url, endpoints, useSecurity);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Finds the endpoint that best matches the current settings.
|
|
/// </summary>
|
|
/// <param name="application">The application configuration.</param>
|
|
/// <param name="discoveryUrl">The discovery URL.</param>
|
|
/// <param name="useSecurity">if set to <c>true</c> select an endpoint that uses security.</param>
|
|
/// <returns>The best available endpoint.</returns>
|
|
public static EndpointDescription SelectEndpoint(
|
|
ApplicationConfiguration application,
|
|
string discoveryUrl,
|
|
bool useSecurity)
|
|
{
|
|
return SelectEndpoint(application, discoveryUrl, useSecurity, DefaultDiscoverTimeout);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Finds the endpoint that best matches the current settings.
|
|
/// </summary>
|
|
/// <param name="application">The application configuration.</param>
|
|
/// <param name="discoveryUrl">The discovery URL.</param>
|
|
/// <param name="useSecurity">if set to <c>true</c> select an endpoint that uses security.</param>
|
|
/// <param name="discoverTimeout">The timeout for the discover operation.</param>
|
|
/// <returns>The best available endpoint.</returns>
|
|
public static EndpointDescription SelectEndpoint(
|
|
ApplicationConfiguration application,
|
|
string discoveryUrl,
|
|
bool useSecurity,
|
|
int discoverTimeout
|
|
)
|
|
{
|
|
var uri = GetDiscoveryUrl(discoveryUrl);
|
|
var endpointConfiguration = EndpointConfiguration.Create();
|
|
endpointConfiguration.OperationTimeout = discoverTimeout;
|
|
|
|
using (var client = DiscoveryClient.Create(application, uri, endpointConfiguration))
|
|
{
|
|
// Connect to the server's discovery endpoint and find the available configuration.
|
|
Uri url = new Uri(client.Endpoint.EndpointUrl);
|
|
var endpoints = client.GetEndpoints(null);
|
|
var selectedEndpoint = SelectEndpoint(url, endpoints, useSecurity);
|
|
|
|
Uri endpointUrl = Utils.ParseUri(selectedEndpoint.EndpointUrl);
|
|
if (endpointUrl != null && endpointUrl.Scheme == uri.Scheme)
|
|
{
|
|
UriBuilder builder = new UriBuilder(endpointUrl);
|
|
builder.Host = uri.DnsSafeHost;
|
|
builder.Port = uri.Port;
|
|
selectedEndpoint.EndpointUrl = builder.ToString();
|
|
}
|
|
|
|
return selectedEndpoint;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Select the best supported endpoint from an
|
|
/// EndpointDescriptionCollection, with or without security.
|
|
/// </summary>
|
|
/// <param name="url"></param>
|
|
/// <param name="endpoints"></param>
|
|
/// <param name="useSecurity"></param>
|
|
public static EndpointDescription SelectEndpoint(
|
|
Uri url,
|
|
EndpointDescriptionCollection endpoints,
|
|
bool useSecurity)
|
|
{
|
|
EndpointDescription selectedEndpoint = null;
|
|
|
|
// select the best endpoint to use based on the selected URL and the UseSecurity checkbox.
|
|
for (int ii = 0; ii < endpoints.Count; ii++)
|
|
{
|
|
EndpointDescription endpoint = endpoints[ii];
|
|
|
|
// check for a match on the URL scheme.
|
|
if (endpoint.EndpointUrl.StartsWith(url.Scheme, StringComparison.Ordinal))
|
|
{
|
|
// check if security was requested.
|
|
if (useSecurity)
|
|
{
|
|
if (endpoint.SecurityMode == MessageSecurityMode.None)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// skip unsupported security policies
|
|
if (SecurityPolicies.GetDisplayName(endpoint.SecurityPolicyUri) == null)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (endpoint.SecurityMode != MessageSecurityMode.None)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// pick the first available endpoint by default.
|
|
if (selectedEndpoint == null)
|
|
{
|
|
selectedEndpoint = endpoint;
|
|
}
|
|
|
|
|
|
//Select endpoint if it has a higher calculated security level, than the previously selected one
|
|
if (SecuredApplication.CalculateSecurityLevel(endpoint.SecurityMode, endpoint.SecurityPolicyUri)
|
|
> SecuredApplication.CalculateSecurityLevel(selectedEndpoint.SecurityMode, selectedEndpoint.SecurityPolicyUri))
|
|
{
|
|
selectedEndpoint = endpoint;
|
|
}
|
|
}
|
|
}
|
|
|
|
// pick the first available endpoint by default.
|
|
if (selectedEndpoint == null && endpoints.Count > 0)
|
|
{
|
|
selectedEndpoint = endpoints.FirstOrDefault(e => e.EndpointUrl?.StartsWith(url.Scheme, StringComparison.Ordinal) == true);
|
|
}
|
|
|
|
// return the selected endpoint.
|
|
return selectedEndpoint;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert the discoveryUrl to a Uri and modify endpoint as per connection scheme if required.
|
|
/// </summary>
|
|
public static Uri GetDiscoveryUrl(string discoveryUrl)
|
|
{
|
|
// needs to add the '/discovery' back onto non-UA TCP URLs.
|
|
if (Utils.IsUriHttpRelatedScheme(discoveryUrl))
|
|
{
|
|
if (!discoveryUrl.EndsWith(ConfiguredEndpoint.DiscoverySuffix, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
discoveryUrl += ConfiguredEndpoint.DiscoverySuffix;
|
|
}
|
|
}
|
|
|
|
// parse the selected URL.
|
|
return new Uri(discoveryUrl);
|
|
}
|
|
#endregion
|
|
}
|
|
}
|