Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 28 additions & 2 deletions Stack/Opc.Ua.Core/Schema/ApplicationConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -694,7 +694,7 @@ public void Initialize(StreamingContext context)
/// This certificate must contain the application uri.
/// For servers, URLs for each supported protocol must also be present.
/// </remarks>
[DataMember(IsRequired = false, EmitDefaultValue = false, Order = 0)]
[IgnoreDataMember]
public CertificateIdentifier ApplicationCertificate
{
get
Expand Down Expand Up @@ -730,10 +730,19 @@ public CertificateIdentifier ApplicationCertificate
}
}

// This private property exists solely to control serialization of the legacy single
// certificate element. It is emitted only when the configuration was marked deprecated.
[DataMember(Name = "ApplicationCertificate", IsRequired = false, EmitDefaultValue = false, Order = 0)]
private CertificateIdentifier ApplicationCertificateLegacy
{
get => IsDeprecatedConfiguration ? ApplicationCertificate : null;
set => ApplicationCertificate = value;
}

/// <summary>
/// The application instance certificates in use for the application.
/// </summary>
[DataMember(IsRequired = false, EmitDefaultValue = false, Order = 1)]
[IgnoreDataMember]
public CertificateIdentifierCollection ApplicationCertificates
{
get => m_applicationCertificates;
Expand All @@ -745,6 +754,11 @@ public CertificateIdentifierCollection ApplicationCertificates
return;
}

// If both legacy (<ApplicationCertificate>) and modern (<ApplicationCertificates>) elements
// are present during deserialization (as a consequence of previous serialization that included both unintentionally),
// prefer the modern representation and clear the
// deprecated flag when we process the collection below.

var newCertificates = new CertificateIdentifierCollection(value);

// Remove unsupported certificate types
Expand Down Expand Up @@ -791,10 +805,22 @@ public CertificateIdentifierCollection ApplicationCertificates

m_applicationCertificates = newCertificates;

// Presence of the modern collection takes precedence over legacy; clear the flag so
// hybrid configurations are treated as modern.
IsDeprecatedConfiguration = false;
SupportedSecurityPolicies = BuildSupportedSecurityPolicies();
}
}

// This private property exists solely to control the serialization of the modern certificates collection.
// Emit only when the configuration is not marked deprecated.
[DataMember(Name = "ApplicationCertificates", IsRequired = false, EmitDefaultValue = false, Order = 1)]
private CertificateIdentifierCollection ApplicationCertificatesDataContract
{
get => IsDeprecatedConfiguration ? null : ApplicationCertificates;
set => ApplicationCertificates = value;
}

/// <summary>
/// The store containing any additional issuer certificates.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,11 @@
<None Update="Opc.Ua.Configuration.Tests.Config.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="testlegacyconfig.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="testhybridconfig.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
280 changes: 280 additions & 0 deletions Tests/Opc.Ua.Configuration.Tests/SecurityConfigurationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,15 @@
* http://opcfoundation.org/License/MIT/1.00/
* ======================================================================*/

using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
using NUnit.Framework;
using Opc.Ua.Tests;

Expand Down Expand Up @@ -63,6 +71,278 @@ public void InvalidConfigurationThrows(SecurityConfiguration configuration)
Assert.Throws<ServiceResultException>(() => configuration.Validate(telemetry));
}

[Test]
public async Task LoadingConfigurationWithApplicationCertificateShouldMarkItDeprecated()
{
ITelemetryContext telemetry = NUnitTelemetryContext.Create();
var file = Path.Combine(TestContext.CurrentContext.WorkDirectory, "testlegacyconfig.xml");

var serializer = new DataContractSerializer(typeof(ApplicationConfiguration));
using var stream = new FileStream(file, FileMode.Open);
var reloadedConfiguration =
(ApplicationConfiguration)serializer.ReadObject(stream);

Assert.That(
reloadedConfiguration.SecurityConfiguration.IsDeprecatedConfiguration,
Is.True);
}

[Test]
public async Task LoadingConfigurationWithApplicationCertificateAndApplicationCertificatesShouldNotMarkItDeprecated()
{
ITelemetryContext telemetry = NUnitTelemetryContext.Create();
var file = Path.Combine(TestContext.CurrentContext.WorkDirectory, "testhybridconfig.xml");

var serializer = new DataContractSerializer(typeof(ApplicationConfiguration));
using var stream = new FileStream(file, FileMode.Open);
var reloadedConfiguration =
(ApplicationConfiguration)serializer.ReadObject(stream);

Assert.That(
reloadedConfiguration.SecurityConfiguration.IsDeprecatedConfiguration,
Is.False);
}

[Test]
public void SavingConfigurationShouldNotMarkItDeprecated()
{
ITelemetryContext telemetry = NUnitTelemetryContext.Create();

var securityConfiguration = new SecurityConfiguration
{
ApplicationCertificates = new CertificateIdentifierCollection
{
new CertificateIdentifier
{
StoreType = CertificateStoreType.Directory,
StorePath = "pki/own",
CertificateType = ObjectTypeIds.RsaSha256ApplicationCertificateType
}
},
TrustedPeerCertificates = new CertificateTrustList { StorePath = "Test" },
TrustedIssuerCertificates = new CertificateTrustList { StorePath = "Test" }
};

var configuration = new ApplicationConfiguration(telemetry)
{
ApplicationName = "DeprecatedConfigurationTest",
ApplicationUri = "urn:localhost:DeprecatedConfigurationTest",
ApplicationType = ApplicationType.Server,
SecurityConfiguration = securityConfiguration
};

var serializer = new DataContractSerializer(typeof(ApplicationConfiguration));
using var stream = new MemoryStream();
serializer.WriteObject(stream, configuration);
stream.Position = 0;

var reloadedConfiguration =
(ApplicationConfiguration)serializer.ReadObject(stream);

Assert.That(
reloadedConfiguration.SecurityConfiguration.IsDeprecatedConfiguration,
Is.False,
"Deserializing a configuration that uses ApplicationCertificates should not mark it deprecated via the legacy ApplicationCertificate setter.");
}

[Test]
public void DeprecatedConfigurationRoundTripsWithLegacyElement()
{
ITelemetryContext telemetry = NUnitTelemetryContext.Create();
var serializer = new DataContractSerializer(typeof(ApplicationConfiguration));

var configuration = new ApplicationConfiguration(telemetry)
{
ApplicationName = "DeprecatedConfigurationTest",
ApplicationUri = "urn:localhost:DeprecatedConfigurationTest",
ApplicationType = ApplicationType.Server,
SecurityConfiguration = new SecurityConfiguration
{
TrustedPeerCertificates = new CertificateTrustList { StorePath = "Test" },
TrustedIssuerCertificates = new CertificateTrustList { StorePath = "Test" }
}
};

configuration.SecurityConfiguration.ApplicationCertificate = new CertificateIdentifier
{
StoreType = CertificateStoreType.Directory,
StorePath = "pki/own",
CertificateType = ObjectTypeIds.RsaSha256ApplicationCertificateType
};

string xml;
using (var stream = new MemoryStream())
{
serializer.WriteObject(stream, configuration);
xml = Encoding.UTF8.GetString(stream.ToArray());
}

var document = XDocument.Parse(xml);
var roundTripped = (ApplicationConfiguration)serializer.ReadObject(
new MemoryStream(Encoding.UTF8.GetBytes(xml)));

Assert.That(configuration.SecurityConfiguration.IsDeprecatedConfiguration, Is.True);
Assert.That(
document.Descendants(XName.Get("ApplicationCertificate", Namespaces.OpcUaConfig)).Any(),
Is.True,
"Legacy ApplicationCertificate element should be present for deprecated configurations.");
}

[Test]
public void ModernConfigurationOmitsLegacyElement()
{
ITelemetryContext telemetry = NUnitTelemetryContext.Create();
var serializer = new DataContractSerializer(typeof(ApplicationConfiguration));

var configuration = new ApplicationConfiguration(telemetry)
{
ApplicationName = "ModernConfigurationTest",
ApplicationUri = "urn:localhost:ModernConfigurationTest",
ApplicationType = ApplicationType.Server,
SecurityConfiguration = new SecurityConfiguration
{
ApplicationCertificates = new CertificateIdentifierCollection
{
new CertificateIdentifier
{
StoreType = CertificateStoreType.Directory,
StorePath = "pki/own",
CertificateType = ObjectTypeIds.RsaSha256ApplicationCertificateType
}
},
TrustedPeerCertificates = new CertificateTrustList { StorePath = "Test" },
TrustedIssuerCertificates = new CertificateTrustList { StorePath = "Test" }
}
};

string xml;
using (var stream = new MemoryStream())
{
serializer.WriteObject(stream, configuration);
xml = Encoding.UTF8.GetString(stream.ToArray());
}

var document = XDocument.Parse(xml);
var roundTripped = (ApplicationConfiguration)serializer.ReadObject(
new MemoryStream(Encoding.UTF8.GetBytes(xml)));

Assert.That(configuration.SecurityConfiguration.IsDeprecatedConfiguration, Is.False);
Assert.That(
document.Descendants(XName.Get("ApplicationCertificate", Namespaces.OpcUaConfig)).Any(),
Is.False,
"Modern configurations should not emit the legacy ApplicationCertificate element.");
Assert.That(
document.Descendants(XName.Get("ApplicationCertificates", Namespaces.OpcUaConfig)).Any(),
Is.True,
"Modern configurations should emit the ApplicationCertificates element.");
}

[Test]
public void DeprecatedConfigurationOmitsApplicationCertificatesElement()
{
ITelemetryContext telemetry = NUnitTelemetryContext.Create();
var serializer = new DataContractSerializer(typeof(ApplicationConfiguration));

var configuration = new ApplicationConfiguration(telemetry)
{
ApplicationName = "DeprecatedNoListConfig",
ApplicationUri = "urn:localhost:DeprecatedNoListConfig",
ApplicationType = ApplicationType.Server,
SecurityConfiguration = new SecurityConfiguration
{
TrustedPeerCertificates = new CertificateTrustList { StorePath = "Test" },
TrustedIssuerCertificates = new CertificateTrustList { StorePath = "Test" }
}
};

configuration.SecurityConfiguration.ApplicationCertificate = new CertificateIdentifier
{
StoreType = CertificateStoreType.Directory,
StorePath = "pki/own",
CertificateType = ObjectTypeIds.RsaSha256ApplicationCertificateType
};

string xml;
using (var stream = new MemoryStream())
{
serializer.WriteObject(stream, configuration);
xml = Encoding.UTF8.GetString(stream.ToArray());
}

var document = XDocument.Parse(xml);

Assert.That(configuration.SecurityConfiguration.IsDeprecatedConfiguration, Is.True);
Assert.That(
document.Descendants(XName.Get("ApplicationCertificate", Namespaces.OpcUaConfig)).Any(),
Is.True,
"Legacy ApplicationCertificate element should be present for deprecated configurations.");
Assert.That(
document.Descendants(XName.Get("ApplicationCertificates", Namespaces.OpcUaConfig)).Any(),
Is.False,
"Deprecated configurations should not emit the ApplicationCertificates element.");
}

[Test]
public void HybridConfigurationPrefersModernElementOnSave()
{
ITelemetryContext telemetry = NUnitTelemetryContext.Create();
var serializer = new DataContractSerializer(typeof(ApplicationConfiguration));

var legacyCert = new CertificateIdentifier
{
StoreType = CertificateStoreType.Directory,
StorePath = "pki/own",
CertificateType = ObjectTypeIds.RsaSha256ApplicationCertificateType
};

var modernCert = new CertificateIdentifier
{
StoreType = CertificateStoreType.Directory,
StorePath = "pki/own-modern",
CertificateType = ObjectTypeIds.RsaSha256ApplicationCertificateType
};

var configuration = new ApplicationConfiguration(telemetry)
{
ApplicationName = "HybridConfiguration",
ApplicationUri = "urn:localhost:HybridConfiguration",
ApplicationType = ApplicationType.Server,
SecurityConfiguration = new SecurityConfiguration
{
TrustedPeerCertificates = new CertificateTrustList { StorePath = "Test" },
TrustedIssuerCertificates = new CertificateTrustList { StorePath = "Test" }
}
};

// First set legacy to mark deprecated, then set the modern collection.
configuration.SecurityConfiguration.ApplicationCertificate = legacyCert;
configuration.SecurityConfiguration.ApplicationCertificates =
new CertificateIdentifierCollection { modernCert };

string xml;
using (var stream = new MemoryStream())
{
serializer.WriteObject(stream, configuration);
xml = Encoding.UTF8.GetString(stream.ToArray());
}

var document = XDocument.Parse(xml);
var roundTripped = (ApplicationConfiguration)serializer.ReadObject(
new MemoryStream(Encoding.UTF8.GetBytes(xml)));

Assert.That(configuration.SecurityConfiguration.IsDeprecatedConfiguration, Is.False);
Assert.That(roundTripped.SecurityConfiguration.IsDeprecatedConfiguration, Is.False);
Assert.That(
document.Descendants(XName.Get("ApplicationCertificate", Namespaces.OpcUaConfig)).Any(),
Is.False,
"Hybrid configurations should serialize as modern and omit the legacy element.");
Assert.That(
document.Descendants(XName.Get("ApplicationCertificates", Namespaces.OpcUaConfig)).Any(),
Is.True,
"Hybrid configurations should serialize the modern ApplicationCertificates element.");
Assert.That(roundTripped.SecurityConfiguration.ApplicationCertificates.Count, Is.EqualTo(1));
}

private static IEnumerable<TestCaseData> GetInvalidConfigurations()
{
yield return new TestCaseData(
Expand Down
Loading
Loading