mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-22 01:26:20 +08:00
Fix ip address parser, domain name check and reconnect (#333)
* bug fix in cert parser: return normalized ipaddress string instead of hex byte array string * connect with domain cert check, * fix ip address compares, * add dialog to connect anyway if cert domain check fails * fix reconnect case when cert domain check fails
This commit is contained in:
@@ -122,6 +122,7 @@ namespace Opc.Ua.Client
|
||||
m_handle = template.m_handle;
|
||||
m_identity = template.m_identity;
|
||||
m_keepAliveInterval = template.m_keepAliveInterval;
|
||||
m_checkDomain = template.m_checkDomain;
|
||||
|
||||
if (copyEventHandlers)
|
||||
{
|
||||
@@ -137,7 +138,9 @@ namespace Opc.Ua.Client
|
||||
this.AddSubscription(new Subscription(subscription, copyEventHandlers));
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
/// <summary>
|
||||
/// Initializes the channel.
|
||||
/// </summary>
|
||||
@@ -270,6 +273,67 @@ namespace Opc.Ua.Client
|
||||
m_defaultSubscription.Priority = 255;
|
||||
m_defaultSubscription.PublishingEnabled = true;
|
||||
}
|
||||
|
||||
private static void CheckCertificateDomain(ConfiguredEndpoint endpoint)
|
||||
{
|
||||
bool domainFound = false;
|
||||
|
||||
X509Certificate2 serverCertificate = new X509Certificate2(endpoint.Description.ServerCertificate);
|
||||
|
||||
// check the certificate domains.
|
||||
IList<string> domains = Utils.GetDomainsFromCertficate(serverCertificate);
|
||||
|
||||
if (domains != null)
|
||||
{
|
||||
string hostname;
|
||||
string dnsHostName = hostname = endpoint.EndpointUrl.DnsSafeHost;
|
||||
bool isLocalHost = false;
|
||||
if (endpoint.EndpointUrl.HostNameType == UriHostNameType.Dns)
|
||||
{
|
||||
if (dnsHostName.ToLowerInvariant() == "localhost")
|
||||
{
|
||||
isLocalHost = true;
|
||||
}
|
||||
else
|
||||
{ // strip domain names from hostname
|
||||
hostname = dnsHostName.Split('.')[0];
|
||||
}
|
||||
}
|
||||
else
|
||||
{ // dnsHostname is a IPv4 or IPv6 address
|
||||
// normalize ip addresses, cert parser returns normalized addresses
|
||||
hostname = Utils.NormalizedIPAddress(dnsHostName);
|
||||
if (hostname == "127.0.0.1" || hostname == "::1")
|
||||
{
|
||||
isLocalHost = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (isLocalHost)
|
||||
{
|
||||
dnsHostName = Utils.GetFullQualifiedDomainName();
|
||||
hostname = Utils.GetHostName();
|
||||
}
|
||||
|
||||
for (int ii = 0; ii < domains.Count; ii++)
|
||||
{
|
||||
if (String.Compare(hostname, domains[ii], StringComparison.OrdinalIgnoreCase) == 0 ||
|
||||
String.Compare(dnsHostName, domains[ii], StringComparison.OrdinalIgnoreCase) == 0)
|
||||
{
|
||||
domainFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!domainFound)
|
||||
{
|
||||
string message = Utils.Format(
|
||||
"The domain '{0}' is not listed in the server certificate.",
|
||||
endpoint.EndpointUrl.DnsSafeHost);
|
||||
throw new ServiceResultException(StatusCodes.BadCertificateHostNameInvalid, message);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region IDisposable Members
|
||||
@@ -694,8 +758,7 @@ namespace Opc.Ua.Client
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
#region Public Static Methods
|
||||
/// <summary>
|
||||
/// Creates a new communication session with a server by invoking the CreateSession service
|
||||
/// </summary>
|
||||
@@ -813,49 +876,6 @@ namespace Opc.Ua.Client
|
||||
return session;
|
||||
}
|
||||
|
||||
private static void CheckCertificateDomain(ConfiguredEndpoint endpoint)
|
||||
{
|
||||
bool domainFound = false;
|
||||
|
||||
if (endpoint.EndpointUrl.HostNameType != UriHostNameType.Dns)
|
||||
{
|
||||
// ignore endpoints configured with IPv4 / IPv6 addresses
|
||||
return;
|
||||
}
|
||||
|
||||
X509Certificate2 serverCertificate = new X509Certificate2(endpoint.Description.ServerCertificate);
|
||||
|
||||
// check the certificate domains.
|
||||
IList<string> domains = Utils.GetDomainsFromCertficate(serverCertificate);
|
||||
|
||||
if (domains != null)
|
||||
{
|
||||
string [] hostnames = endpoint.EndpointUrl.DnsSafeHost.Split('.');
|
||||
|
||||
if (String.Compare(hostnames[0], "localhost", StringComparison.CurrentCultureIgnoreCase)==0)
|
||||
{
|
||||
hostnames[0] = Utils.GetHostName();
|
||||
}
|
||||
|
||||
for (int ii = 0; ii < domains.Count; ii++)
|
||||
{
|
||||
foreach (string hostname in hostnames)
|
||||
{
|
||||
if (String.Compare(hostname, domains[ii], StringComparison.CurrentCultureIgnoreCase) == 0)
|
||||
{
|
||||
domainFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!domainFound)
|
||||
{
|
||||
throw new ServiceResultException(StatusCodes.BadCertificateHostNameInvalid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Recreates a session based on a specified template.
|
||||
@@ -882,7 +902,8 @@ namespace Opc.Ua.Client
|
||||
template.m_sessionName,
|
||||
(uint)template.m_sessionTimeout,
|
||||
template.m_identity,
|
||||
template.m_preferredLocales);
|
||||
template.m_preferredLocales,
|
||||
template.m_checkDomain);
|
||||
|
||||
// create the subscriptions.
|
||||
foreach (Subscription subscription in session.Subscriptions)
|
||||
@@ -898,7 +919,8 @@ namespace Opc.Ua.Client
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
#endregion
|
||||
#region Delegates and Events
|
||||
/// <summary>
|
||||
/// Used to handle renews of user identity tokens before reconnect.
|
||||
/// </summary>
|
||||
@@ -914,7 +936,8 @@ namespace Opc.Ua.Client
|
||||
}
|
||||
|
||||
private event RenewUserIdentityEventHandler m_RenewUserIdentity;
|
||||
|
||||
#endregion
|
||||
#region Public Methods
|
||||
/// <summary>
|
||||
/// Reconnects to the server after a network failure.
|
||||
/// </summary>
|
||||
@@ -1938,11 +1961,11 @@ namespace Opc.Ua.Client
|
||||
/// <param name="checkDomain">If set to <c>true</c> then the domain in the certificate must match the endpoint used.</param>
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")]
|
||||
public void Open(
|
||||
string sessionName,
|
||||
uint sessionTimeout,
|
||||
string sessionName,
|
||||
uint sessionTimeout,
|
||||
IUserIdentity identity,
|
||||
IList<string> preferredLocales,
|
||||
bool checkDomain)
|
||||
bool checkDomain)
|
||||
{
|
||||
// check connection state.
|
||||
lock (SyncRoot)
|
||||
@@ -1991,7 +2014,6 @@ namespace Opc.Ua.Client
|
||||
// validate the server certificate.
|
||||
X509Certificate2 serverCertificate = null;
|
||||
byte[] certificateData = m_endpoint.Description.ServerCertificate;
|
||||
|
||||
if (certificateData != null && certificateData.Length > 0 && requireEncryption)
|
||||
{
|
||||
serverCertificate = Utils.ParseCertificateBlob(certificateData);
|
||||
@@ -2001,6 +2023,8 @@ namespace Opc.Ua.Client
|
||||
{
|
||||
CheckCertificateDomain(m_endpoint);
|
||||
}
|
||||
// save for reconnect
|
||||
m_checkDomain = checkDomain;
|
||||
|
||||
//X509Certificate2Collection certificateChain = Utils.ParseCertificateChainBlob(certificateData);
|
||||
//if (certificateChain.Count > 0)
|
||||
@@ -2291,7 +2315,7 @@ namespace Opc.Ua.Client
|
||||
{
|
||||
for (int i = 0; i < certificateResults.Count; i++)
|
||||
{
|
||||
Utils.Trace("ActivateSession result[{0}] = {1}", i, certificateResults[i]);
|
||||
Utils.Trace("ActivateSession result[{0}] = {1}", i, certificateResults[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2719,7 +2743,8 @@ namespace Opc.Ua.Client
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
#region Close Methods
|
||||
/// <summary>
|
||||
/// Disconnects from the server and frees any network resources.
|
||||
/// </summary>
|
||||
@@ -2805,7 +2830,9 @@ namespace Opc.Ua.Client
|
||||
Dispose();
|
||||
return result;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Subscription Methods
|
||||
/// <summary>
|
||||
/// Adds a subscription to the session.
|
||||
/// </summary>
|
||||
@@ -2909,8 +2936,9 @@ namespace Opc.Ua.Client
|
||||
|
||||
return true;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Browse Methods
|
||||
#region Browse Methods
|
||||
/// <summary>
|
||||
/// Invokes the Browse service.
|
||||
/// </summary>
|
||||
@@ -3056,9 +3084,9 @@ namespace Opc.Ua.Client
|
||||
|
||||
return responseHeader;
|
||||
}
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region BrowseNext Methods
|
||||
#region BrowseNext Methods
|
||||
/// <summary>
|
||||
/// Invokes the BrowseNext service.
|
||||
/// </summary>
|
||||
@@ -3148,8 +3176,9 @@ namespace Opc.Ua.Client
|
||||
|
||||
return responseHeader;
|
||||
}
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region Call Methods
|
||||
/// <summary>
|
||||
/// Calls the specified method and returns the output arguments.
|
||||
/// </summary>
|
||||
@@ -3204,9 +3233,9 @@ namespace Opc.Ua.Client
|
||||
|
||||
return outputArguments;
|
||||
}
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region Protected Methods
|
||||
#region Protected Methods
|
||||
/// <summary>
|
||||
/// Returns the software certificates assigned to the application.
|
||||
/// </summary>
|
||||
@@ -3564,9 +3593,9 @@ namespace Opc.Ua.Client
|
||||
|
||||
return true;
|
||||
}
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region Publish Methods
|
||||
#region Publish Methods
|
||||
/// <summary>
|
||||
/// Sends an additional publish request.
|
||||
/// </summary>
|
||||
@@ -3675,7 +3704,7 @@ namespace Opc.Ua.Client
|
||||
Utils.Trace("Error - Publish call finished. ResultCode={0}; SubscriptionId={1};", code.ToString(), subscriptionId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// nothing more to do if session changed.
|
||||
if (sessionId != SessionId)
|
||||
{
|
||||
@@ -4041,9 +4070,9 @@ namespace Opc.Ua.Client
|
||||
Utils.Trace(e, "Session: Unexpected rrror while raising Notification event.");
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region Private Fields
|
||||
#region Private Fields
|
||||
private SubscriptionAcknowledgementCollection m_acknowledgementsToSend;
|
||||
private Dictionary<uint, uint> m_latestAcknowledgementsSent;
|
||||
private List<Subscription> m_subscriptions;
|
||||
@@ -4061,6 +4090,7 @@ namespace Opc.Ua.Client
|
||||
private ConfiguredEndpoint m_endpoint;
|
||||
private X509Certificate2 m_instanceCertificate;
|
||||
private X509Certificate2Collection m_instanceCertificateChain;
|
||||
private bool m_checkDomain;
|
||||
private List<IUserIdentity> m_identityHistory;
|
||||
|
||||
private string m_sessionName;
|
||||
@@ -4094,16 +4124,16 @@ namespace Opc.Ua.Client
|
||||
private event PublishErrorEventHandler m_PublishError;
|
||||
private event EventHandler m_SubscriptionsChanged;
|
||||
private event EventHandler m_SessionClosing;
|
||||
#endregion
|
||||
#endregion
|
||||
}
|
||||
|
||||
#region KeepAliveEventArgs Class
|
||||
#region KeepAliveEventArgs Class
|
||||
/// <summary>
|
||||
/// The event arguments provided when a keep alive response arrives.
|
||||
/// </summary>
|
||||
public class KeepAliveEventArgs : EventArgs
|
||||
{
|
||||
#region Constructors
|
||||
#region Constructors
|
||||
/// <summary>
|
||||
/// Creates a new instance.
|
||||
/// </summary>
|
||||
@@ -4116,9 +4146,9 @@ namespace Opc.Ua.Client
|
||||
m_currentState = currentState;
|
||||
m_currentTime = currentTime;
|
||||
}
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
#region Public Properties
|
||||
/// <summary>
|
||||
/// Gets the status associated with the keep alive operation.
|
||||
/// </summary>
|
||||
@@ -4151,29 +4181,29 @@ namespace Opc.Ua.Client
|
||||
get { return m_cancelKeepAlive; }
|
||||
set { m_cancelKeepAlive = value; }
|
||||
}
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region Private Fields
|
||||
#region Private Fields
|
||||
private ServiceResult m_status;
|
||||
private ServerState m_currentState;
|
||||
private DateTime m_currentTime;
|
||||
private bool m_cancelKeepAlive;
|
||||
#endregion
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The delegate used to receive keep alive notifications.
|
||||
/// </summary>
|
||||
public delegate void KeepAliveEventHandler(Session session, KeepAliveEventArgs e);
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region NotificationEventArgs Class
|
||||
#region NotificationEventArgs Class
|
||||
/// <summary>
|
||||
/// Represents the event arguments provided when a new notification message arrives.
|
||||
/// </summary>
|
||||
public class NotificationEventArgs : EventArgs
|
||||
{
|
||||
#region Constructors
|
||||
#region Constructors
|
||||
/// <summary>
|
||||
/// Creates a new instance.
|
||||
/// </summary>
|
||||
@@ -4186,9 +4216,9 @@ namespace Opc.Ua.Client
|
||||
m_notificationMessage = notificationMessage;
|
||||
m_stringTable = stringTable;
|
||||
}
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
#region Public Properties
|
||||
/// <summary>
|
||||
/// Gets the subscription that the notification applies to.
|
||||
/// </summary>
|
||||
@@ -4212,28 +4242,28 @@ namespace Opc.Ua.Client
|
||||
{
|
||||
get { return m_stringTable; }
|
||||
}
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region Private Fields
|
||||
#region Private Fields
|
||||
private Subscription m_subscription;
|
||||
private NotificationMessage m_notificationMessage;
|
||||
private IList<string> m_stringTable;
|
||||
#endregion
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The delegate used to receive publish notifications.
|
||||
/// </summary>
|
||||
public delegate void NotificationEventHandler(Session session, NotificationEventArgs e);
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region PublishErrorEventArgs Class
|
||||
#region PublishErrorEventArgs Class
|
||||
/// <summary>
|
||||
/// Represents the event arguments provided when a publish error occurs.
|
||||
/// </summary>
|
||||
public class PublishErrorEventArgs : EventArgs
|
||||
{
|
||||
#region Constructors
|
||||
#region Constructors
|
||||
/// <summary>
|
||||
/// Creates a new instance.
|
||||
/// </summary>
|
||||
@@ -4251,9 +4281,9 @@ namespace Opc.Ua.Client
|
||||
m_subscriptionId = subscriptionId;
|
||||
m_sequenceNumber = sequenceNumber;
|
||||
}
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
#region Public Properties
|
||||
/// <summary>
|
||||
/// Gets the status associated with the keep alive operation.
|
||||
/// </summary>
|
||||
@@ -4277,18 +4307,18 @@ namespace Opc.Ua.Client
|
||||
{
|
||||
get { return m_sequenceNumber; }
|
||||
}
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region Private Fields
|
||||
#region Private Fields
|
||||
private uint m_subscriptionId;
|
||||
private uint m_sequenceNumber;
|
||||
private ServiceResult m_status;
|
||||
#endregion
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The delegate used to receive pubish error notifications.
|
||||
/// </summary>
|
||||
public delegate void PublishErrorEventHandler(Session session, PublishErrorEventArgs e);
|
||||
#endregion
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -28,10 +28,7 @@
|
||||
* ======================================================================*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using Opc.Ua;
|
||||
|
||||
namespace Opc.Ua.Client
|
||||
{
|
||||
@@ -39,7 +36,7 @@ namespace Opc.Ua.Client
|
||||
/// Attempts to reconnect to the server.
|
||||
/// </summary>
|
||||
public class SessionReconnectHandler : IDisposable
|
||||
{
|
||||
{
|
||||
#region IDisposable Members
|
||||
/// <summary>
|
||||
/// Frees any unmanaged resources.
|
||||
@@ -66,8 +63,8 @@ namespace Opc.Ua.Client
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
/// <summary>
|
||||
/// Gets the session managed by the handler.
|
||||
@@ -164,14 +161,16 @@ namespace Opc.Ua.Client
|
||||
ServiceResultException sre = exception as ServiceResultException;
|
||||
|
||||
// check if the server endpoint could not be reached.
|
||||
if ((sre != null && (sre.StatusCode == StatusCodes.BadTcpInternalError || sre.StatusCode == StatusCodes.BadCommunicationError)) ||
|
||||
if ((sre != null &&
|
||||
(sre.StatusCode == StatusCodes.BadTcpInternalError ||
|
||||
sre.StatusCode == StatusCodes.BadCommunicationError ||
|
||||
sre.StatusCode == StatusCodes.BadNotConnected)) ||
|
||||
exception is System.ServiceModel.EndpointNotFoundException)
|
||||
{
|
||||
// check if reconnecting is still an option.
|
||||
if (m_session.LastKeepAliveTime.AddMilliseconds(m_session.SessionTimeout) > DateTime.UtcNow)
|
||||
{
|
||||
Utils.Trace("Calling OnReconnectSession in {0} ms.", m_reconnectPeriod);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,7 +193,7 @@ namespace Opc.Ua.Sample.Controls
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
GuiUtils.HandleException(this.Text, MethodBase.GetCurrentMethod(), exception);
|
||||
GuiUtils.HandleException(this.Text, MethodBase.GetCurrentMethod(), exception);
|
||||
e.UpdateControl = false;
|
||||
}
|
||||
}
|
||||
@@ -206,7 +206,7 @@ namespace Opc.Ua.Sample.Controls
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// GuiUtils.HandleException(this.Text, MethodBase.GetCurrentMethod(), exception);
|
||||
// GuiUtils.HandleException(this.Text, MethodBase.GetCurrentMethod(), exception);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,7 +349,7 @@ namespace Opc.Ua.Sample.Controls
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
GuiUtils.HandleException(this.Text, MethodBase.GetCurrentMethod(), exception);
|
||||
GuiUtils.HandleException(this.Text, MethodBase.GetCurrentMethod(), exception);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -387,7 +387,7 @@ namespace Opc.Ua.Sample.Controls
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
GuiUtils.HandleException(this.Text, MethodBase.GetCurrentMethod(), exception);
|
||||
GuiUtils.HandleException(this.Text, MethodBase.GetCurrentMethod(), exception);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -425,7 +425,7 @@ namespace Opc.Ua.Sample.Controls
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
GuiUtils.HandleException(this.Text, MethodBase.GetCurrentMethod(), exception);
|
||||
GuiUtils.HandleException(this.Text, MethodBase.GetCurrentMethod(), exception);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -440,7 +440,7 @@ namespace Opc.Ua.Sample.Controls
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
GuiUtils.HandleException(this.Text, MethodBase.GetCurrentMethod(), exception);
|
||||
GuiUtils.HandleException(this.Text, MethodBase.GetCurrentMethod(), exception);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -457,7 +457,7 @@ namespace Opc.Ua.Sample.Controls
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Utils.Trace(exception, "Could not register with the LDS");
|
||||
Utils.Trace(exception, "Could not register with the LDS");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,19 +27,14 @@
|
||||
* http://opcfoundation.org/License/MIT/1.00/
|
||||
* ======================================================================*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Data;
|
||||
using System.Drawing;
|
||||
using System.Text;
|
||||
using System.Windows.Forms;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
|
||||
using Opc.Ua.Client;
|
||||
using Opc.Ua.Client.Controls;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace Opc.Ua.Sample.Controls
|
||||
{
|
||||
@@ -58,6 +53,7 @@ namespace Opc.Ua.Sample.Controls
|
||||
private const string m_BrowseCertificates = "<Browse...>";
|
||||
private static long m_Counter = 0;
|
||||
private IList<string> m_preferredLocales;
|
||||
private bool m_checkDomain = true;
|
||||
#endregion
|
||||
|
||||
#region Public Interface
|
||||
@@ -163,7 +159,7 @@ namespace Opc.Ua.Sample.Controls
|
||||
|
||||
Cursor = Cursors.WaitCursor;
|
||||
|
||||
ThreadPool.QueueUserWorkItem(Open, new object[] { m_session, SessionNameTB.Text, identity, m_preferredLocales });
|
||||
ThreadPool.QueueUserWorkItem(Open, new object[] { m_session, SessionNameTB.Text, identity, m_preferredLocales, m_checkDomain });
|
||||
|
||||
CancelBTN.Enabled = false;
|
||||
OkBTN.Enabled = false;
|
||||
@@ -194,6 +190,35 @@ namespace Opc.Ua.Sample.Controls
|
||||
{
|
||||
Cursor = Cursors.Default;
|
||||
|
||||
ServiceResultException sre = e as ServiceResultException;
|
||||
if (sre != null)
|
||||
{
|
||||
if (m_checkDomain && sre.StatusCode == StatusCodes.BadCertificateHostNameInvalid)
|
||||
{
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
|
||||
buffer.AppendFormat(sre.Message);
|
||||
buffer.AppendFormat("\r\n\r\nRetry without certificate hostname validation?");
|
||||
|
||||
DialogResult result = MessageBox.Show(
|
||||
buffer.ToString(),
|
||||
"Exception: BadCertificateHostNameInvalid",
|
||||
MessageBoxButtons.YesNo,
|
||||
MessageBoxIcon.Warning);
|
||||
|
||||
if (result == DialogResult.Yes)
|
||||
{
|
||||
m_checkDomain = false;
|
||||
OkBTN.Enabled = true;
|
||||
OkBTN.PerformClick();
|
||||
return;
|
||||
}
|
||||
|
||||
DialogResult = DialogResult.OK;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (e != null)
|
||||
{
|
||||
GuiUtils.HandleException(this.Text, MethodBase.GetCurrentMethod(), (Exception)e);
|
||||
@@ -228,9 +253,10 @@ namespace Opc.Ua.Sample.Controls
|
||||
string sessionName = ((object[])state)[1] as string;
|
||||
IUserIdentity identity = ((object[])state)[2] as IUserIdentity;
|
||||
IList<string> preferredLocales = ((object[])state)[3] as IList<string>;
|
||||
bool? checkDomain = ((object[])state)[4] as bool?;
|
||||
|
||||
// open the session.
|
||||
session.Open(sessionName, (uint)session.SessionTimeout, identity, preferredLocales);
|
||||
session.Open(sessionName, (uint)session.SessionTimeout, identity, preferredLocales, checkDomain ?? true);
|
||||
|
||||
OpenComplete(null);
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ namespace Opc.Ua.Sample.Controls
|
||||
private const string m_BrowseCertificates = "<Browse...>";
|
||||
private static long m_Counter = 0;
|
||||
private IList<string> m_preferredLocales;
|
||||
private bool m_checkDomain = false;
|
||||
#endregion
|
||||
|
||||
#region Public Interface
|
||||
@@ -171,7 +172,7 @@ namespace Opc.Ua.Sample.Controls
|
||||
|
||||
CancelBTN.IsEnabled = false;
|
||||
OkBTN.IsEnabled = false;
|
||||
object state = new object[] { m_session, SessionNameTB.Text, identity, m_preferredLocales };
|
||||
object state = new object[] { m_session, SessionNameTB.Text, identity, m_preferredLocales, m_checkDomain };
|
||||
await Task.Run(() => m_session = Open(state));
|
||||
dialogPopup.IsOpen = false;
|
||||
}
|
||||
@@ -204,9 +205,10 @@ namespace Opc.Ua.Sample.Controls
|
||||
string sessionName = ((object[])state)[1] as string;
|
||||
IUserIdentity identity = ((object[])state)[2] as IUserIdentity;
|
||||
IList<string> preferredLocales = ((object[])state)[3] as IList<string>;
|
||||
bool? checkDomain = ((object[])state)[4] as bool?;
|
||||
|
||||
// open the session.
|
||||
session.Open(sessionName, (uint)session.SessionTimeout, identity, preferredLocales);
|
||||
session.Open(sessionName, (uint)session.SessionTimeout, identity, preferredLocales, checkDomain ?? true);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
@@ -91,7 +92,7 @@ namespace Opc.Ua
|
||||
buffer.Append(m_uris[ii]);
|
||||
}
|
||||
|
||||
for (int ii = 0; ii < m_domainNames.Count; ii++)
|
||||
for (int ii = 0; ii < m_domainNames.Count; ii++)
|
||||
{
|
||||
if (buffer.Length > 0)
|
||||
{
|
||||
@@ -208,7 +209,7 @@ namespace Opc.Ua
|
||||
domainNames.Add(generalName.Name.ToString());
|
||||
break;
|
||||
case Org.BouncyCastle.Asn1.X509.GeneralName.IPAddress:
|
||||
ipAddresses.Add(generalName.Name.ToString());
|
||||
ipAddresses.Add(IPAddressToString(Org.BouncyCastle.Asn1.DerOctetString.GetInstance(generalName.Name).GetOctets()));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -219,6 +220,27 @@ namespace Opc.Ua
|
||||
m_ipAddresses = new ReadOnlyList<string>(ipAddresses);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a normalized IPv4 or IPv6 address from a 4 byte or 16 byte array.
|
||||
/// </summary>
|
||||
private string IPAddressToString(byte[] encodedIPAddress)
|
||||
{
|
||||
try
|
||||
{
|
||||
IPAddress address = new IPAddress(encodedIPAddress);
|
||||
return address.ToString();
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw new ServiceResultException(
|
||||
StatusCodes.BadCertificateInvalid,
|
||||
"Certificate contains invalid IP address.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse certificate for alternate name extension.
|
||||
/// </summary>
|
||||
private void Parse(byte[] data)
|
||||
{
|
||||
if (base.Oid.Value == SubjectAltNameOid ||
|
||||
@@ -252,6 +274,6 @@ namespace Opc.Ua
|
||||
private ReadOnlyList<string> m_uris;
|
||||
private ReadOnlyList<string> m_domainNames;
|
||||
private ReadOnlyList<string> m_ipAddresses;
|
||||
#endregion
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,9 +23,9 @@ namespace Opc.Ua
|
||||
/// <summary>
|
||||
/// Loads the configuration section for an application.
|
||||
/// </summary>
|
||||
public class ApplicationConfigurationSection
|
||||
{
|
||||
#region IConfigurationSectionHandler Members
|
||||
public class ApplicationConfigurationSection
|
||||
{
|
||||
#region IConfigurationSectionHandler Members
|
||||
/// <summary>
|
||||
/// Creates the configuration object from the configuration section.
|
||||
/// </summary>
|
||||
@@ -33,21 +33,21 @@ namespace Opc.Ua
|
||||
/// <param name="configContext">The configuration context object.</param>
|
||||
/// <param name="section">The section as XML node.</param>
|
||||
/// <returns>The created section handler object.</returns>
|
||||
public object Create(object parent, object configContext, System.Xml.XmlNode section)
|
||||
{
|
||||
public object Create(object parent, object configContext, System.Xml.XmlNode section)
|
||||
{
|
||||
if (section == null)
|
||||
{
|
||||
throw new ArgumentNullException("section");
|
||||
}
|
||||
|
||||
XmlNode element = section.FirstChild;
|
||||
XmlNode element = section.FirstChild;
|
||||
|
||||
while (element != null && typeof(XmlElement) != element.GetType())
|
||||
{
|
||||
element = element.NextSibling;
|
||||
}
|
||||
|
||||
XmlReader reader = XmlReader.Create(new StringReader(element.OuterXml));
|
||||
while (element != null && typeof(XmlElement) != element.GetType())
|
||||
{
|
||||
element = element.NextSibling;
|
||||
}
|
||||
|
||||
XmlReader reader = XmlReader.Create(new StringReader(element.OuterXml));
|
||||
|
||||
try
|
||||
{
|
||||
@@ -59,54 +59,54 @@ namespace Opc.Ua
|
||||
{
|
||||
reader.Dispose();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
/// <summary>
|
||||
/// Represents the location of a configuration file.
|
||||
/// </summary>
|
||||
[DataContract(Namespace = Namespaces.OpcUaConfig)]
|
||||
public class ConfigurationLocation
|
||||
{
|
||||
#region Persistent Properties
|
||||
#region Persistent Properties
|
||||
/// <summary>
|
||||
/// Gets or sets the relative or absolute path to the configuration file.
|
||||
/// </summary>
|
||||
/// <value>The file path.</value>
|
||||
[DataMember(IsRequired=true, Order=0)]
|
||||
[DataMember(IsRequired = true, Order = 0)]
|
||||
public string FilePath
|
||||
{
|
||||
get { return m_filePath; }
|
||||
set { m_filePath = value; }
|
||||
}
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region Private Fields
|
||||
#region Private Fields
|
||||
private string m_filePath;
|
||||
#endregion
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stores the configurable configuration information for a UA application.
|
||||
/// </summary>
|
||||
public partial class ApplicationConfiguration
|
||||
{
|
||||
#region Public Methods
|
||||
{
|
||||
#region Public Methods
|
||||
/// <summary>
|
||||
/// Gets the file that was used to load the configuration.
|
||||
/// </summary>
|
||||
/// <value>The source file path.</value>
|
||||
public string SourceFilePath
|
||||
{
|
||||
get { return m_sourceFilePath; }
|
||||
get { return m_sourceFilePath; }
|
||||
}
|
||||
|
||||
|
||||
public CertificateValidator CertificateValidator
|
||||
{
|
||||
get { return m_certificateValidator; }
|
||||
get { return m_certificateValidator; }
|
||||
set { m_certificateValidator = value; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns the domain names which the server is configured to use.
|
||||
/// </summary>
|
||||
@@ -154,9 +154,13 @@ namespace Opc.Ua
|
||||
|
||||
string domainName = url.DnsSafeHost;
|
||||
|
||||
if (String.Compare(domainName, "localhost", StringComparison.OrdinalIgnoreCase) == 0)
|
||||
if (url.HostNameType == UriHostNameType.Dns)
|
||||
{
|
||||
domainName = Utils.GetHostName();
|
||||
domainName = Utils.ReplaceLocalhost(domainName);
|
||||
}
|
||||
else // IPv4/IPv6 address
|
||||
{
|
||||
domainName = Utils.NormalizedIPAddress(domainName);
|
||||
}
|
||||
|
||||
if (!Utils.FindStringIgnoreCase(domainNames, domainName))
|
||||
@@ -167,7 +171,7 @@ namespace Opc.Ua
|
||||
|
||||
return domainNames;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates the message context from the configuration.
|
||||
/// </summary>
|
||||
@@ -198,7 +202,7 @@ namespace Opc.Ua
|
||||
[Obsolete("Warning: Behavoir changed return a copy instead of a reference. Should call CreateMessageContext() instead.")]
|
||||
public ServiceMessageContext MessageContext
|
||||
{
|
||||
get
|
||||
get
|
||||
{
|
||||
if (m_messageContext == null)
|
||||
{
|
||||
@@ -230,7 +234,7 @@ namespace Opc.Ua
|
||||
public static async Task<ApplicationConfiguration> Load(string sectionName, ApplicationType applicationType, Type systemType)
|
||||
{
|
||||
string filePath = GetFilePathFromAppConfig(sectionName);
|
||||
|
||||
|
||||
FileInfo file = new FileInfo(filePath);
|
||||
|
||||
if (!file.Exists)
|
||||
@@ -244,7 +248,7 @@ namespace Opc.Ua
|
||||
|
||||
return await Load(file, applicationType, systemType);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Loads but does not validate the application configuration from a configuration section.
|
||||
/// </summary>
|
||||
@@ -376,7 +380,7 @@ namespace Opc.Ua
|
||||
/// <param name="filePath">The file path.</param>
|
||||
/// <remarks>Calls GetType() on the current instance and passes that to the DataContractSerializer.</remarks>
|
||||
public void SaveToFile(string filePath)
|
||||
{
|
||||
{
|
||||
Stream ostrm = File.Open(filePath, FileMode.Create, FileAccess.ReadWrite);
|
||||
|
||||
XmlWriterSettings settings = new XmlWriterSettings();
|
||||
@@ -504,7 +508,7 @@ namespace Opc.Ua
|
||||
if (filePath == null)
|
||||
{
|
||||
filePath = m_clientConfiguration.EndpointCacheFilePath;
|
||||
|
||||
|
||||
if (!Utils.IsPathRooted(filePath))
|
||||
{
|
||||
FileInfo sourceFile = new FileInfo(this.SourceFilePath);
|
||||
@@ -584,16 +588,16 @@ namespace Opc.Ua
|
||||
public void UpdateExtension(Type type, object value)
|
||||
{
|
||||
}
|
||||
#endregion
|
||||
#endregion
|
||||
}
|
||||
|
||||
#region TraceConfiguration Class
|
||||
#region TraceConfiguration Class
|
||||
/// <summary>
|
||||
/// Specifies parameters used for tracing.
|
||||
/// </summary>
|
||||
public partial class TraceConfiguration
|
||||
{
|
||||
#region Public Methods
|
||||
#region Public Methods
|
||||
/// <summary>
|
||||
/// Applies the trace settings to the current process.
|
||||
/// </summary>
|
||||
@@ -611,17 +615,17 @@ namespace Opc.Ua
|
||||
Utils.SetTraceOutput(Utils.TraceOutput.DebugAndFile);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
#endregion
|
||||
}
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region ServerBaseConfiguration Class
|
||||
#region ServerBaseConfiguration Class
|
||||
/// <summary>
|
||||
/// Specifies the configuration for a server application.
|
||||
/// </summary>
|
||||
public partial class ServerBaseConfiguration
|
||||
{
|
||||
#region Public Methods
|
||||
#region Public Methods
|
||||
/// <summary>
|
||||
/// Validates the configuration.
|
||||
/// </summary>
|
||||
@@ -632,17 +636,17 @@ namespace Opc.Ua
|
||||
m_securityPolicies.Add(new ServerSecurityPolicy());
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
#endregion
|
||||
}
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region ServerConfiguration Class
|
||||
#region ServerConfiguration Class
|
||||
/// <summary>
|
||||
/// Specifies the configuration for a server application.
|
||||
/// </summary>
|
||||
public partial class ServerConfiguration : ServerBaseConfiguration
|
||||
{
|
||||
#region Public Methods
|
||||
#region Public Methods
|
||||
/// <summary>
|
||||
/// Validates the configuration.
|
||||
/// </summary>
|
||||
@@ -655,17 +659,17 @@ namespace Opc.Ua
|
||||
m_userTokenPolicies.Add(new UserTokenPolicy());
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
#endregion
|
||||
}
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region ClientConfiguration Class
|
||||
#region ClientConfiguration Class
|
||||
/// <summary>
|
||||
/// The configuration for a client application.
|
||||
/// </summary>
|
||||
public partial class ClientConfiguration
|
||||
{
|
||||
#region Public Methods
|
||||
#region Public Methods
|
||||
/// <summary>
|
||||
/// Validates the configuration.
|
||||
/// </summary>
|
||||
@@ -676,7 +680,7 @@ namespace Opc.Ua
|
||||
WellKnownDiscoveryUrls.AddRange(Utils.DiscoveryUrls);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
#endregion
|
||||
}
|
||||
#endregion
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -920,6 +920,41 @@ namespace Opc.Ua
|
||||
return Dns.GetHostName().Split('.')[0].ToLowerInvariant();
|
||||
}
|
||||
|
||||
public static string GetFullQualifiedDomainName()
|
||||
{
|
||||
string domainName = null;
|
||||
try
|
||||
{
|
||||
#if !NETSTANDARD1_4 && !NETSTANDARD1_3
|
||||
domainName = Dns.GetHostEntry("localhost").HostName;
|
||||
#endif
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
if (String.IsNullOrEmpty(domainName))
|
||||
{
|
||||
return Dns.GetHostName();
|
||||
}
|
||||
return domainName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Normalize ipv4/ipv6 address for comparisons.
|
||||
/// </summary>
|
||||
public static string NormalizedIPAddress(string ipAddress)
|
||||
{
|
||||
try
|
||||
{
|
||||
IPAddress normalizedAddress = IPAddress.Parse(ipAddress);
|
||||
return normalizedAddress.ToString();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return ipAddress;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces the localhost domain with the current host name.
|
||||
/// </summary>
|
||||
|
||||
Reference in New Issue
Block a user