From dbef06aa82abb94698d4c1e41458e8187180fdff Mon Sep 17 00:00:00 2001 From: Martin Regen Date: Thu, 25 Jan 2018 08:55:26 -0700 Subject: [PATCH] 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 --- .../SDK/Opc.Ua.Client/Session.cs | 210 ++++++++++-------- .../Opc.Ua.Client/SessionReconnectHandler.cs | 15 +- .../Samples/Controls.Net4/ClientForm.cs | 14 +- .../Controls.Net4/Sessions/SessionOpenDlg.cs | 52 +++-- .../Controls/Sessions/SessionOpenDlg.xaml.cs | 6 +- .../X509SubjectAltNameExtension.cs | 28 ++- .../Configuration/ApplicationConfiguration.cs | 106 ++++----- Stack/Opc.Ua.Core/Types/Utils/Utils.cs | 35 +++ 8 files changed, 292 insertions(+), 174 deletions(-) diff --git a/SampleApplications/SDK/Opc.Ua.Client/Session.cs b/SampleApplications/SDK/Opc.Ua.Client/Session.cs index bcfeef10be..437bcb7777 100644 --- a/SampleApplications/SDK/Opc.Ua.Client/Session.cs +++ b/SampleApplications/SDK/Opc.Ua.Client/Session.cs @@ -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 /// /// Initializes the channel. /// @@ -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 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 /// /// Creates a new communication session with a server by invoking the CreateSession service /// @@ -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 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); - } - } - } - /// /// 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 /// /// Used to handle renews of user identity tokens before reconnect. /// @@ -914,7 +936,8 @@ namespace Opc.Ua.Client } private event RenewUserIdentityEventHandler m_RenewUserIdentity; - + #endregion + #region Public Methods /// /// Reconnects to the server after a network failure. /// @@ -1938,11 +1961,11 @@ namespace Opc.Ua.Client /// If set to true then the domain in the certificate must match the endpoint used. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")] public void Open( - string sessionName, - uint sessionTimeout, + string sessionName, + uint sessionTimeout, IUserIdentity identity, IList 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 /// /// Disconnects from the server and frees any network resources. /// @@ -2805,7 +2830,9 @@ namespace Opc.Ua.Client Dispose(); return result; } + #endregion + #region Subscription Methods /// /// Adds a subscription to the session. /// @@ -2909,8 +2936,9 @@ namespace Opc.Ua.Client return true; } + #endregion -#region Browse Methods + #region Browse Methods /// /// Invokes the Browse service. /// @@ -3056,9 +3084,9 @@ namespace Opc.Ua.Client return responseHeader; } -#endregion + #endregion -#region BrowseNext Methods + #region BrowseNext Methods /// /// Invokes the BrowseNext service. /// @@ -3148,8 +3176,9 @@ namespace Opc.Ua.Client return responseHeader; } -#endregion + #endregion + #region Call Methods /// /// Calls the specified method and returns the output arguments. /// @@ -3204,9 +3233,9 @@ namespace Opc.Ua.Client return outputArguments; } -#endregion + #endregion -#region Protected Methods + #region Protected Methods /// /// Returns the software certificates assigned to the application. /// @@ -3564,9 +3593,9 @@ namespace Opc.Ua.Client return true; } -#endregion + #endregion -#region Publish Methods + #region Publish Methods /// /// Sends an additional publish request. /// @@ -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 m_latestAcknowledgementsSent; private List 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 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 /// /// The event arguments provided when a keep alive response arrives. /// public class KeepAliveEventArgs : EventArgs { -#region Constructors + #region Constructors /// /// Creates a new instance. /// @@ -4116,9 +4146,9 @@ namespace Opc.Ua.Client m_currentState = currentState; m_currentTime = currentTime; } -#endregion + #endregion -#region Public Properties + #region Public Properties /// /// Gets the status associated with the keep alive operation. /// @@ -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 } /// /// The delegate used to receive keep alive notifications. /// public delegate void KeepAliveEventHandler(Session session, KeepAliveEventArgs e); -#endregion + #endregion -#region NotificationEventArgs Class + #region NotificationEventArgs Class /// /// Represents the event arguments provided when a new notification message arrives. /// public class NotificationEventArgs : EventArgs { -#region Constructors + #region Constructors /// /// Creates a new instance. /// @@ -4186,9 +4216,9 @@ namespace Opc.Ua.Client m_notificationMessage = notificationMessage; m_stringTable = stringTable; } -#endregion + #endregion -#region Public Properties + #region Public Properties /// /// Gets the subscription that the notification applies to. /// @@ -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 m_stringTable; -#endregion + #endregion } /// /// The delegate used to receive publish notifications. /// public delegate void NotificationEventHandler(Session session, NotificationEventArgs e); -#endregion + #endregion -#region PublishErrorEventArgs Class + #region PublishErrorEventArgs Class /// /// Represents the event arguments provided when a publish error occurs. /// public class PublishErrorEventArgs : EventArgs { -#region Constructors + #region Constructors /// /// Creates a new instance. /// @@ -4251,9 +4281,9 @@ namespace Opc.Ua.Client m_subscriptionId = subscriptionId; m_sequenceNumber = sequenceNumber; } -#endregion + #endregion -#region Public Properties + #region Public Properties /// /// Gets the status associated with the keep alive operation. /// @@ -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 } /// /// The delegate used to receive pubish error notifications. /// public delegate void PublishErrorEventHandler(Session session, PublishErrorEventArgs e); -#endregion + #endregion } diff --git a/SampleApplications/SDK/Opc.Ua.Client/SessionReconnectHandler.cs b/SampleApplications/SDK/Opc.Ua.Client/SessionReconnectHandler.cs index c66c24f8c7..10c53c4078 100644 --- a/SampleApplications/SDK/Opc.Ua.Client/SessionReconnectHandler.cs +++ b/SampleApplications/SDK/Opc.Ua.Client/SessionReconnectHandler.cs @@ -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. /// public class SessionReconnectHandler : IDisposable - { + { #region IDisposable Members /// /// Frees any unmanaged resources. @@ -66,8 +63,8 @@ namespace Opc.Ua.Client } } } - #endregion - + #endregion + #region Public Methods /// /// 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; } } diff --git a/SampleApplications/Samples/Controls.Net4/ClientForm.cs b/SampleApplications/Samples/Controls.Net4/ClientForm.cs index 014432d82c..3f9878e5f4 100644 --- a/SampleApplications/Samples/Controls.Net4/ClientForm.cs +++ b/SampleApplications/Samples/Controls.Net4/ClientForm.cs @@ -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"); } } diff --git a/SampleApplications/Samples/Controls.Net4/Sessions/SessionOpenDlg.cs b/SampleApplications/Samples/Controls.Net4/Sessions/SessionOpenDlg.cs index 12e333767e..146cd97ea1 100644 --- a/SampleApplications/Samples/Controls.Net4/Sessions/SessionOpenDlg.cs +++ b/SampleApplications/Samples/Controls.Net4/Sessions/SessionOpenDlg.cs @@ -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 = ""; private static long m_Counter = 0; private IList 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 preferredLocales = ((object[])state)[3] as IList; + 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); } diff --git a/SampleApplications/Samples/Controls/Sessions/SessionOpenDlg.xaml.cs b/SampleApplications/Samples/Controls/Sessions/SessionOpenDlg.xaml.cs index 0becec5177..7409bd1601 100644 --- a/SampleApplications/Samples/Controls/Sessions/SessionOpenDlg.xaml.cs +++ b/SampleApplications/Samples/Controls/Sessions/SessionOpenDlg.xaml.cs @@ -54,6 +54,7 @@ namespace Opc.Ua.Sample.Controls private const string m_BrowseCertificates = ""; private static long m_Counter = 0; private IList 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 preferredLocales = ((object[])state)[3] as IList; + 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; } diff --git a/Stack/Opc.Ua.Core/Security/Certificates/X509SubjectAltNameExtension.cs b/Stack/Opc.Ua.Core/Security/Certificates/X509SubjectAltNameExtension.cs index 7d11741436..8e7fd386d3 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/X509SubjectAltNameExtension.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/X509SubjectAltNameExtension.cs @@ -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(ipAddresses); } + /// + /// Create a normalized IPv4 or IPv6 address from a 4 byte or 16 byte array. + /// + 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."); + } + } + + /// + /// Parse certificate for alternate name extension. + /// private void Parse(byte[] data) { if (base.Oid.Value == SubjectAltNameOid || @@ -252,6 +274,6 @@ namespace Opc.Ua private ReadOnlyList m_uris; private ReadOnlyList m_domainNames; private ReadOnlyList m_ipAddresses; -#endregion + #endregion } } diff --git a/Stack/Opc.Ua.Core/Stack/Configuration/ApplicationConfiguration.cs b/Stack/Opc.Ua.Core/Stack/Configuration/ApplicationConfiguration.cs index 7506107ac9..51c66e444d 100644 --- a/Stack/Opc.Ua.Core/Stack/Configuration/ApplicationConfiguration.cs +++ b/Stack/Opc.Ua.Core/Stack/Configuration/ApplicationConfiguration.cs @@ -23,9 +23,9 @@ namespace Opc.Ua /// /// Loads the configuration section for an application. /// - public class ApplicationConfigurationSection - { -#region IConfigurationSectionHandler Members + public class ApplicationConfigurationSection + { + #region IConfigurationSectionHandler Members /// /// Creates the configuration object from the configuration section. /// @@ -33,21 +33,21 @@ namespace Opc.Ua /// The configuration context object. /// The section as XML node. /// The created section handler object. - 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 + } /// /// Represents the location of a configuration file. /// [DataContract(Namespace = Namespaces.OpcUaConfig)] public class ConfigurationLocation { -#region Persistent Properties + #region Persistent Properties /// /// Gets or sets the relative or absolute path to the configuration file. /// /// The file path. - [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 } /// /// Stores the configurable configuration information for a UA application. /// public partial class ApplicationConfiguration - { -#region Public Methods + { + #region Public Methods /// /// Gets the file that was used to load the configuration. /// /// The source file path. 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; } } - + /// /// Returns the domain names which the server is configured to use. /// @@ -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; } - + /// /// Creates the message context from the configuration. /// @@ -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 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); } - + /// /// Loads but does not validate the application configuration from a configuration section. /// @@ -376,7 +380,7 @@ namespace Opc.Ua /// The file path. /// Calls GetType() on the current instance and passes that to the DataContractSerializer. 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 /// /// Specifies parameters used for tracing. /// public partial class TraceConfiguration { -#region Public Methods + #region Public Methods /// /// Applies the trace settings to the current process. /// @@ -611,17 +615,17 @@ namespace Opc.Ua Utils.SetTraceOutput(Utils.TraceOutput.DebugAndFile); } } -#endregion + #endregion } -#endregion + #endregion -#region ServerBaseConfiguration Class + #region ServerBaseConfiguration Class /// /// Specifies the configuration for a server application. /// public partial class ServerBaseConfiguration { -#region Public Methods + #region Public Methods /// /// Validates the configuration. /// @@ -632,17 +636,17 @@ namespace Opc.Ua m_securityPolicies.Add(new ServerSecurityPolicy()); } } -#endregion + #endregion } -#endregion + #endregion -#region ServerConfiguration Class + #region ServerConfiguration Class /// /// Specifies the configuration for a server application. /// public partial class ServerConfiguration : ServerBaseConfiguration { -#region Public Methods + #region Public Methods /// /// Validates the configuration. /// @@ -655,17 +659,17 @@ namespace Opc.Ua m_userTokenPolicies.Add(new UserTokenPolicy()); } } -#endregion + #endregion } -#endregion + #endregion -#region ClientConfiguration Class + #region ClientConfiguration Class /// /// The configuration for a client application. /// public partial class ClientConfiguration { -#region Public Methods + #region Public Methods /// /// Validates the configuration. /// @@ -676,7 +680,7 @@ namespace Opc.Ua WellKnownDiscoveryUrls.AddRange(Utils.DiscoveryUrls); } } -#endregion + #endregion } -#endregion + #endregion } diff --git a/Stack/Opc.Ua.Core/Types/Utils/Utils.cs b/Stack/Opc.Ua.Core/Types/Utils/Utils.cs index 60ceb5be09..bac02233a1 100644 --- a/Stack/Opc.Ua.Core/Types/Utils/Utils.cs +++ b/Stack/Opc.Ua.Core/Types/Utils/Utils.cs @@ -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; + } + + /// + /// Normalize ipv4/ipv6 address for comparisons. + /// + public static string NormalizedIPAddress(string ipAddress) + { + try + { + IPAddress normalizedAddress = IPAddress.Parse(ipAddress); + return normalizedAddress.ToString(); + } + catch + { + return ipAddress; + } + } + /// /// Replaces the localhost domain with the current host name. ///