diff --git a/Libraries/Opc.Ua.Client/ReverseConnectManager.cs b/Libraries/Opc.Ua.Client/ReverseConnectManager.cs
index d8a998545..bf5afe2c2 100644
--- a/Libraries/Opc.Ua.Client/ReverseConnectManager.cs
+++ b/Libraries/Opc.Ua.Client/ReverseConnectManager.cs
@@ -152,18 +152,21 @@ public Registration(
}
///
- /// Register with the server certificate.
+ /// Register with the server certificate to extract the application Uri.
///
- ///
- ///
- ///
+ ///
+ /// The first Uri in the subject alternate name field is considered the application Uri.
+ ///
+ /// The server certificate with the application Uri.
+ /// The endpoint Url of the server.
+ /// The connection to use.
public Registration(
X509Certificate2 serverCertificate,
Uri endpointUrl,
EventHandler onConnectionWaiting) :
this(endpointUrl, onConnectionWaiting)
{
- ServerUri = X509Utils.GetApplicationUriFromCertificate(serverCertificate);
+ ServerUri = X509Utils.GetApplicationUrisFromCertificate(serverCertificate).FirstOrDefault();
}
private Registration(
diff --git a/Libraries/Opc.Ua.Client/Session/Session.cs b/Libraries/Opc.Ua.Client/Session/Session.cs
index 2bef15dfd..8d5798cab 100644
--- a/Libraries/Opc.Ua.Client/Session/Session.cs
+++ b/Libraries/Opc.Ua.Client/Session/Session.cs
@@ -2338,7 +2338,6 @@ public void Open(
if (requireEncryption)
{
- ValidateServerCertificateApplicationUri(serverCertificate);
if (checkDomain)
{
m_configuration.CertificateValidator.Validate(serverCertificateChain, m_endpoint);
@@ -2417,24 +2416,24 @@ public void Open(
if (!successCreateSession)
{
base.CreateSession(
- null,
- clientDescription,
- m_endpoint.Description.Server.ApplicationUri,
- m_endpoint.EndpointUrl.ToString(),
- sessionName,
- clientNonce,
- clientCertificateChainData != null ? clientCertificateChainData : clientCertificateData,
- sessionTimeout,
- (uint)MessageContext.MaxMessageSize,
- out sessionId,
- out sessionCookie,
- out m_sessionTimeout,
- out serverNonce,
- out serverCertificateData,
- out serverEndpoints,
- out serverSoftwareCertificates,
- out serverSignature,
- out m_maxRequestMessageSize);
+ null,
+ clientDescription,
+ m_endpoint.Description.Server.ApplicationUri,
+ m_endpoint.EndpointUrl.ToString(),
+ sessionName,
+ clientNonce,
+ clientCertificateChainData != null ? clientCertificateChainData : clientCertificateData,
+ sessionTimeout,
+ (uint)MessageContext.MaxMessageSize,
+ out sessionId,
+ out sessionCookie,
+ out m_sessionTimeout,
+ out serverNonce,
+ out serverCertificateData,
+ out serverEndpoints,
+ out serverSoftwareCertificates,
+ out serverSignature,
+ out m_maxRequestMessageSize);
}
// save session id.
@@ -2456,6 +2455,8 @@ public void Open(
ValidateServerEndpoints(serverEndpoints);
+ ValidateServerCertificateApplicationUri(m_endpoint, serverCertificate);
+
ValidateServerSignature(serverCertificate, serverSignature, clientCertificateData, clientCertificateChainData, clientNonce);
HandleSignedSoftwareCertificates(serverSoftwareCertificates);
@@ -5352,26 +5353,53 @@ private void OpenValidateIdentity(
!String.IsNullOrEmpty(identityPolicy.SecurityPolicyUri);
}
}
+
///
- /// Validates the ServerCertificate ApplicationUri to match the ApplicationUri of the Endpoint for an open call (Spec Part 4 5.4.1)
+ /// Validates the ServerCertificate ApplicationUri to match the ApplicationUri
+ /// of the Endpoint (Spec Part 4 5.4.1) returned by the CreateSessionResponse.
+ /// Ensure the endpoint was matched in
+ /// with the applicationUri of the server description before the validation.
///
- private void ValidateServerCertificateApplicationUri(
- X509Certificate2 serverCertificate)
+ private static void ValidateServerCertificateApplicationUri(ConfiguredEndpoint endpoint, X509Certificate2 serverCertificate)
{
- var applicationUri = m_endpoint?.Description?.Server?.ApplicationUri;
- //check is only neccessary if the ApplicatioUri is specified for the Endpoint
- if (string.IsNullOrEmpty(applicationUri))
+ if (serverCertificate != null)
{
- throw ServiceResultException.Create(
- StatusCodes.BadSecurityChecksFailed,
- "No ApplicationUri is specified for the server in the EndpointDescription.");
- }
- string certificateApplicationUri = X509Utils.GetApplicationUriFromCertificate(serverCertificate);
- if (!string.Equals(certificateApplicationUri, applicationUri, StringComparison.Ordinal))
- {
- throw ServiceResultException.Create(
- StatusCodes.BadSecurityChecksFailed,
- "Server did not return a Certificate matching the ApplicationUri specified in the EndpointDescription.");
+ var applicationUri = endpoint?.Description?.Server?.ApplicationUri;
+
+ // check that an ApplicatioUri is specified for the Endpoint
+ if (string.IsNullOrEmpty(applicationUri))
+ {
+ throw ServiceResultException.Create(
+ StatusCodes.BadCertificateUriInvalid,
+ "Server did not return an ApplicationUri in the EndpointDescription.");
+ }
+
+ var certificateApplicationUris = X509Utils.GetApplicationUrisFromCertificate(serverCertificate);
+ if (certificateApplicationUris.Count == 0)
+ {
+ throw ServiceResultException.Create(
+ StatusCodes.BadCertificateUriInvalid,
+ "The Server Certificate ({1}) does not contain an applicationUri.",
+ serverCertificate.Subject);
+ }
+
+ bool noMatch = true;
+ foreach (var certificateApplicationUri in certificateApplicationUris)
+ {
+ if (string.Equals(certificateApplicationUri, applicationUri, StringComparison.Ordinal))
+ {
+ noMatch = false;
+ break;
+ }
+ }
+
+ if (noMatch)
+ {
+ throw ServiceResultException.Create(
+ StatusCodes.BadCertificateUriInvalid,
+ "The Application in the EndpointDescription ({0}) is not in the Server Certificate ({1}).",
+ applicationUri, serverCertificate.Subject);
+ }
}
}
@@ -5538,7 +5566,7 @@ private void ValidateServerEndpoints(EndpointDescriptionCollection serverEndpoin
}
}
- // find the matching description (TBD - check domains against certificate).
+ // find the matching description (TBD - check domains and application URI against certificate).
bool found = false;
var foundDescription = FindMatchingDescription(serverEndpoints, m_endpoint.Description, true);
diff --git a/Libraries/Opc.Ua.Client/Session/SessionAsync.cs b/Libraries/Opc.Ua.Client/Session/SessionAsync.cs
index 1b26df310..68bb5b988 100644
--- a/Libraries/Opc.Ua.Client/Session/SessionAsync.cs
+++ b/Libraries/Opc.Ua.Client/Session/SessionAsync.cs
@@ -93,7 +93,6 @@ public async Task OpenAsync(
if (requireEncryption)
{
- ValidateServerCertificateApplicationUri(serverCertificate);
if (checkDomain)
{
await m_configuration.CertificateValidator.ValidateAsync(serverCertificateChain, m_endpoint, ct).ConfigureAwait(false);
@@ -201,6 +200,8 @@ public async Task OpenAsync(
ValidateServerEndpoints(serverEndpoints);
+ ValidateServerCertificateApplicationUri(m_endpoint, serverCertificate);
+
ValidateServerSignature(serverCertificate, serverSignature, clientCertificateData, clientCertificateChainData, clientNonce);
HandleSignedSoftwareCertificates(serverSoftwareCertificates);
diff --git a/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs b/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs
index 64dba21e1..3c447a4b5 100644
--- a/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs
+++ b/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs
@@ -671,23 +671,38 @@ private async Task CheckApplicationInstanceCertificateAsync(
}
}
- // check uri.
- string applicationUri = X509Utils.GetApplicationUriFromCertificate(certificate);
+ // default application uri
+ string applicationUri = configuration.ApplicationUri;
- if (String.IsNullOrEmpty(applicationUri))
+ // check certificate Uri and find a match
+ var applicationUris = X509Utils.GetApplicationUrisFromCertificate(certificate);
+ bool foundMatch = false;
+ foreach (var appUri in applicationUris)
{
- string message = "The Application URI could not be read from the certificate. Use certificate anyway?";
- if (!await ApplicationInstance.ApproveMessageAsync(message, silent).ConfigureAwait(false))
+ if (configuration.ApplicationUri.Equals(appUri, StringComparison.Ordinal))
{
- return false;
+ foundMatch = true;
+ break;
}
}
- else if (!configuration.ApplicationUri.Equals(applicationUri, StringComparison.Ordinal))
+
+ if (!foundMatch && applicationUris.Count > 0)
{
- Utils.LogInfo("Updated the ApplicationUri: {0} --> {1}", configuration.ApplicationUri, applicationUri);
+ foundMatch = true;
+ applicationUri = applicationUris[0];
+ Utils.LogInfo("Updating the ApplicationUri: {0} --> {1}", configuration.ApplicationUri, applicationUris[0]);
configuration.ApplicationUri = applicationUri;
}
+ if (!foundMatch)
+ {
+ string message = "The Application URI could not be found in the certificate. Use certificate anyway?";
+ if (!await ApplicationInstance.ApproveMessageAsync(message, silent).ConfigureAwait(false))
+ {
+ return false;
+ }
+ }
+
Utils.LogInfo("Using the ApplicationUri: {0}", applicationUri);
// update configuration.
diff --git a/Libraries/Opc.Ua.Gds.Client.Common/CertificateWrapper.cs b/Libraries/Opc.Ua.Gds.Client.Common/CertificateWrapper.cs
index 4d929cb0f..5cc3514b1 100644
--- a/Libraries/Opc.Ua.Gds.Client.Common/CertificateWrapper.cs
+++ b/Libraries/Opc.Ua.Gds.Client.Common/CertificateWrapper.cs
@@ -29,6 +29,7 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Runtime.Serialization;
using System.Security.Cryptography.X509Certificates;
using Opc.Ua.Security.Certificates;
@@ -213,7 +214,7 @@ public string ApplicationUri
{
try
{
- return X509Utils.GetApplicationUriFromCertificate(Certificate);
+ return X509Utils.GetApplicationUrisFromCertificate(Certificate).FirstOrDefault();
}
catch (Exception e)
{
diff --git a/Libraries/Opc.Ua.Security.Certificates/Extensions/X509SubjectAltNameExtension.cs b/Libraries/Opc.Ua.Security.Certificates/Extensions/X509SubjectAltNameExtension.cs
index d799c1871..9ca7be21b 100644
--- a/Libraries/Opc.Ua.Security.Certificates/Extensions/X509SubjectAltNameExtension.cs
+++ b/Libraries/Opc.Ua.Security.Certificates/Extensions/X509SubjectAltNameExtension.cs
@@ -118,7 +118,23 @@ public X509SubjectAltNameExtension(
{
Oid = new Oid(SubjectAltName2Oid, kFriendlyName);
Critical = false;
- Initialize(applicationUri, domainNames);
+ Initialize(new string[] { applicationUri }, domainNames);
+ RawData = Encode();
+ m_decoded = true;
+ }
+
+ ///
+ /// Build the Subject Alternative name extension (for OPC UA application certs).
+ ///
+ /// The application Uri
+ /// The domain names. DNS Hostnames, IPv4 or IPv6 addresses
+ public X509SubjectAltNameExtension(
+ IEnumerable applicationUris,
+ IEnumerable domainNames)
+ {
+ Oid = new Oid(SubjectAltName2Oid, kFriendlyName);
+ Critical = false;
+ Initialize(applicationUris, domainNames);
RawData = Encode();
m_decoded = true;
}
@@ -402,14 +418,17 @@ private void Decode(byte[] data)
///
/// Initialize the Subject Alternative name extension.
///
- /// The application Uri
+ /// The application Uris
/// The general names. DNS Hostnames, IPv4 or IPv6 addresses
- private void Initialize(string applicationUri, IEnumerable generalNames)
+ private void Initialize(IEnumerable applicationUris, IEnumerable generalNames)
{
var uris = new List();
var domainNames = new List();
var ipAddresses = new List();
- uris.Add(applicationUri);
+ foreach (string applicationUri in applicationUris)
+ {
+ uris.Add(applicationUri);
+ }
foreach (string generalName in generalNames)
{
switch (Uri.CheckHostName(generalName))
diff --git a/Libraries/Opc.Ua.Server/Server/StandardServer.cs b/Libraries/Opc.Ua.Server/Server/StandardServer.cs
index a5d19b836..575bcfb78 100644
--- a/Libraries/Opc.Ua.Server/Server/StandardServer.cs
+++ b/Libraries/Opc.Ua.Server/Server/StandardServer.cs
@@ -380,23 +380,34 @@ public override ResponseHeader CreateSession(
if (context.SecurityPolicyUri != SecurityPolicies.None)
{
- string certificateApplicationUri = X509Utils.GetApplicationUriFromCertificate(parsedClientCertificate);
-
- // verify if applicationUri from ApplicationDescription matches the applicationUri in the client certificate.
- if (!String.IsNullOrEmpty(certificateApplicationUri) &&
- !String.IsNullOrEmpty(clientDescription.ApplicationUri) &&
- certificateApplicationUri != clientDescription.ApplicationUri)
+ // verify if applicationUri from ApplicationDescription matches the applicationUris in the client certificate.
+ if (!String.IsNullOrEmpty(clientDescription.ApplicationUri))
{
- // report the AuditCertificateDataMismatch event for invalid uri
- ServerInternal?.ReportAuditCertificateDataMismatchEvent(parsedClientCertificate, null, clientDescription.ApplicationUri, StatusCodes.BadCertificateUriInvalid);
+ bool noMatch = true;
+ var certificateApplicationUris = X509Utils.GetApplicationUrisFromCertificate(parsedClientCertificate);
+ foreach (var certificateApplicationUri in certificateApplicationUris)
+ {
+ if (!String.IsNullOrEmpty(certificateApplicationUri) &&
+ certificateApplicationUri == clientDescription.ApplicationUri)
+ {
+ noMatch = false;
+ break;
+ }
+ }
- throw ServiceResultException.Create(
- StatusCodes.BadCertificateUriInvalid,
- "The URI specified in the ApplicationDescription {0} does not match the URI in the Certificate: {1}.",
- clientDescription.ApplicationUri, certificateApplicationUri);
- }
+ if (noMatch)
+ {
+ // report the AuditCertificateDataMismatch event for invalid uri
+ ServerInternal?.ReportAuditCertificateDataMismatchEvent(parsedClientCertificate, null, clientDescription.ApplicationUri, StatusCodes.BadCertificateUriInvalid);
+
+ throw ServiceResultException.Create(
+ StatusCodes.BadCertificateUriInvalid,
+ "The URI specified in the ApplicationDescription {0} does not match the URIs in the Certificate.",
+ clientDescription.ApplicationUri);
+ }
- CertificateValidator.Validate(clientCertificateChain);
+ CertificateValidator.Validate(clientCertificateChain);
+ }
}
}
catch (Exception e)
@@ -2327,7 +2338,7 @@ public bool RegisterWithDiscoveryServer()
{
client.RegisterServer(requestHeader, m_registrationInfo);
}
-
+
m_registeredWithDiscoveryServer = m_registrationInfo.IsOnline;
return true;
}
@@ -3082,7 +3093,7 @@ protected override void OnServerStopping()
// attempt graceful shutdown the server.
try
{
-
+
if (m_maxRegistrationInterval > 0 && m_registeredWithDiscoveryServer)
{
// unregister from Discovery Server if registered before
diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateFactory.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateFactory.cs
index 2a2fde212..1a145fc77 100644
--- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateFactory.cs
+++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateFactory.cs
@@ -319,10 +319,10 @@ public static byte[] CreateSigningRequest(
}
}
- string applicationUri = X509Utils.GetApplicationUriFromCertificate(certificate);
+ var applicationUris = X509Utils.GetApplicationUrisFromCertificate(certificate);
// Subject Alternative Name
- var subjectAltName = new X509SubjectAltNameExtension(applicationUri, domainNames);
+ var subjectAltName = new X509SubjectAltNameExtension(applicationUris, domainNames);
request.CertificateExtensions.Add(new X509Extension(subjectAltName, false));
using (RSA rsa = certificate.GetRSAPrivateKey())
diff --git a/Stack/Opc.Ua.Core/Security/Certificates/X509Utils.cs b/Stack/Opc.Ua.Core/Security/Certificates/X509Utils.cs
index e47f1a4c2..9c0a02372 100644
--- a/Stack/Opc.Ua.Core/Security/Certificates/X509Utils.cs
+++ b/Stack/Opc.Ua.Core/Security/Certificates/X509Utils.cs
@@ -126,6 +126,7 @@ public static int GetRSAPublicKeySize(X509Certificate2 certificate)
///
/// The certificate.
/// The application URI.
+ [Obsolete("Use GetApplicationUrisFromCertificate instead. The certificate may contain more than one Uri.")]
public static string GetApplicationUriFromCertificate(X509Certificate2 certificate)
{
// extract the alternate domains from the subject alternate name extension.
@@ -140,6 +141,25 @@ public static string GetApplicationUriFromCertificate(X509Certificate2 certifica
return string.Empty;
}
+ ///
+ /// Extracts the application URIs specified in the certificate.
+ ///
+ /// The certificate.
+ /// The application URIs.
+ public static IReadOnlyList GetApplicationUrisFromCertificate(X509Certificate2 certificate)
+ {
+ // extract the alternate domains from the subject alternate name extension.
+ X509SubjectAltNameExtension alternateName = X509Extensions.FindExtension(certificate);
+
+ // get the application uris.
+ if (alternateName != null && alternateName.Uris != null)
+ {
+ return alternateName.Uris;
+ }
+
+ return new List();
+ }
+
///
/// Check if certificate has an application urn.
///
diff --git a/Stack/Opc.Ua.Core/Stack/Server/ServerBase.cs b/Stack/Opc.Ua.Core/Stack/Server/ServerBase.cs
index 9d75de2b0..15804de89 100644
--- a/Stack/Opc.Ua.Core/Stack/Server/ServerBase.cs
+++ b/Stack/Opc.Ua.Core/Stack/Server/ServerBase.cs
@@ -1405,7 +1405,7 @@ protected virtual void OnServerStarting(ApplicationConfiguration configuration)
// assign a unique identifier if none specified.
if (String.IsNullOrEmpty(configuration.ApplicationUri))
{
- configuration.ApplicationUri = X509Utils.GetApplicationUriFromCertificate(InstanceCertificate);
+ configuration.ApplicationUri = X509Utils.GetApplicationUrisFromCertificate(InstanceCertificate).FirstOrDefault();
if (String.IsNullOrEmpty(configuration.ApplicationUri))
{
diff --git a/Stack/Opc.Ua.Core/Types/Utils/Utils.cs b/Stack/Opc.Ua.Core/Types/Utils/Utils.cs
index 6816af4e8..550a01e14 100644
--- a/Stack/Opc.Ua.Core/Types/Utils/Utils.cs
+++ b/Stack/Opc.Ua.Core/Types/Utils/Utils.cs
@@ -2529,7 +2529,7 @@ public static void UpdateExtension(ref XmlElementCollection extensions, XmlQu
extensions.Add(document.DocumentElement);
}
}
-#endregion
+ #endregion
#region Reflection Helper Functions
///
diff --git a/Tests/Opc.Ua.Core.Tests/Security/Certificates/CertificateFactoryTest.cs b/Tests/Opc.Ua.Core.Tests/Security/Certificates/CertificateFactoryTest.cs
index b6e28a798..16273c14e 100644
--- a/Tests/Opc.Ua.Core.Tests/Security/Certificates/CertificateFactoryTest.cs
+++ b/Tests/Opc.Ua.Core.Tests/Security/Certificates/CertificateFactoryTest.cs
@@ -435,8 +435,8 @@ public static void VerifyApplicationCert(ApplicationTestData testApp, X509Certif
Assert.True(domainNames.Contains(domainName, StringComparer.OrdinalIgnoreCase));
}
Assert.True(subjectAlternateName.Uris.Count == 1);
- var applicationUri = X509Utils.GetApplicationUriFromCertificate(cert);
- TestContext.Out.WriteLine("ApplicationUri: ");
+ var applicationUri = X509Utils.GetApplicationUrisFromCertificate(cert).FirstOrDefault();
+ TestContext.Out.WriteLine("ApplicationUris: ");
TestContext.Out.WriteLine(applicationUri);
Assert.AreEqual(testApp.ApplicationUri, applicationUri);
}
diff --git a/Tests/Opc.Ua.Gds.Tests/X509TestUtils.cs b/Tests/Opc.Ua.Gds.Tests/X509TestUtils.cs
index e61ad47a1..1b0dc51e8 100644
--- a/Tests/Opc.Ua.Gds.Tests/X509TestUtils.cs
+++ b/Tests/Opc.Ua.Gds.Tests/X509TestUtils.cs
@@ -156,7 +156,7 @@ public static void VerifySignedApplicationCert(ApplicationTestData testApp, byte
Assert.True(domainNames.Contains(domainName, StringComparer.OrdinalIgnoreCase));
}
Assert.True(subjectAlternateName.Uris.Count == 1);
- var applicationUri = X509Utils.GetApplicationUriFromCertificate(signedCert);
+ var applicationUri = X509Utils.GetApplicationUrisFromCertificate(signedCert).FirstOrDefault();
Assert.True(testApp.ApplicationRecord.ApplicationUri == applicationUri);
}
}