mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-18 22:55:08 +08:00
3c4661d2bf
## Failure MCP auto-accept mutated the shared application certificate manager and accepted every certificate error, so one permissive connection could affect concurrent or later strict sessions. The durable-subscription sample also serialized complete user identity tokens, including user-name passwords and issued bearer tokens. ## Fix - Isolate auto-accept certificate managers/configurations per connection and keep their lifetime with the managed session. - Auto-accept only `BadCertificateUntrusted`; retain strict validation for all other certificate errors and shared sessions. - Version the durable store and reject the previous unsafe format. - Remove user-name passwords before persistence and reject issued-token subscriptions rather than storing bearer credentials. - Add focused tools tests plus solution, project, and durable-subscription docs. ## Reference - OPC 10000-4 Session identity-token and application-certificate security. - Split from #4021. ## Tests - `Opc.Ua.Tools.Tests` Release: 7 passed on net10.0; 4 passed on net48. - `Quickstarts.Servers` Release builds passed on net10.0 and net48 with zero warnings/errors. - `Opc.Ua.Mcp` Release net10.0 build passed with zero warnings/errors. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
138 lines
5.1 KiB
C#
138 lines
5.1 KiB
C#
/* ========================================================================
|
|
* Copyright (c) 2005-2026 The OPC Foundation, Inc. All rights reserved.
|
|
*
|
|
* OPC Foundation MIT License 1.00
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person
|
|
* obtaining a copy of this software and associated documentation
|
|
* files (the "Software"), to deal in the Software without
|
|
* restriction, including without limitation the rights to use,
|
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following
|
|
* conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be
|
|
* included in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
* OTHER DEALINGS IN THE SOFTWARE.
|
|
*
|
|
* The complete license agreement can be found here:
|
|
* http://opcfoundation.org/License/MIT/1.00/
|
|
* ======================================================================*/
|
|
|
|
using System;
|
|
using System.IO;
|
|
using System.Text;
|
|
using NUnit.Framework;
|
|
using Opc.Ua.Server;
|
|
using Quickstarts.Servers;
|
|
|
|
namespace Opc.Ua.Tools.Tests
|
|
{
|
|
[TestFixture]
|
|
public class SubscriptionStoreTests
|
|
{
|
|
[SetUp]
|
|
public void SetUp()
|
|
{
|
|
m_telemetry = (DefaultTelemetry)DefaultTelemetry.Create(static _ => { });
|
|
m_messageContext = ServiceMessageContext.Create(m_telemetry);
|
|
}
|
|
|
|
[TearDown]
|
|
public void TearDown()
|
|
{
|
|
m_telemetry.Dispose();
|
|
}
|
|
|
|
[Test]
|
|
public void EncodeSubscriptionRemovesUserNamePassword()
|
|
{
|
|
var identityToken = new UserNameIdentityToken
|
|
{
|
|
PolicyId = "username",
|
|
UserName = "operator",
|
|
Password = new ByteString(Encoding.UTF8.GetBytes("secret")),
|
|
EncryptionAlgorithm = SecurityPolicies.Basic256Sha256
|
|
};
|
|
var subscription = new StoredSubscription
|
|
{
|
|
UserIdentityToken = identityToken
|
|
};
|
|
|
|
using var encoder = new BinaryEncoder(m_messageContext);
|
|
SubscriptionStore.EncodeSubscription(encoder, subscription);
|
|
byte[] encoded = encoder.CloseAndReturnBuffer()!;
|
|
|
|
using var decoder = new BinaryDecoder(encoded, m_messageContext);
|
|
StoredSubscription decoded = SubscriptionStore.DecodeSubscription(decoder);
|
|
|
|
Assert.That(decoded.UserIdentityToken, Is.TypeOf<UserNameIdentityToken>());
|
|
var decodedToken = (UserNameIdentityToken)decoded.UserIdentityToken!;
|
|
Assert.That(decodedToken.PolicyId, Is.EqualTo(identityToken.PolicyId));
|
|
Assert.That(decodedToken.UserName, Is.EqualTo(identityToken.UserName));
|
|
Assert.That(decodedToken.Password.IsNull, Is.True);
|
|
Assert.That(decodedToken.EncryptionAlgorithm, Is.Null);
|
|
Assert.That(identityToken.Password.IsNull, Is.False);
|
|
}
|
|
|
|
[Test]
|
|
public void EncodeSubscriptionRejectsIssuedBearerToken()
|
|
{
|
|
var subscription = new StoredSubscription
|
|
{
|
|
UserIdentityToken = new IssuedIdentityToken
|
|
{
|
|
PolicyId = Profiles.JwtUserToken,
|
|
TokenData = new ByteString(Encoding.UTF8.GetBytes("bearer-token"))
|
|
}
|
|
};
|
|
|
|
using var encoder = new BinaryEncoder(m_messageContext);
|
|
|
|
Assert.That(
|
|
() => SubscriptionStore.EncodeSubscription(encoder, subscription),
|
|
Throws.TypeOf<NotSupportedException>());
|
|
}
|
|
|
|
[Test]
|
|
public void StoreHeaderAcceptsCurrentVersion()
|
|
{
|
|
using var encoder = new BinaryEncoder(m_messageContext);
|
|
SubscriptionStore.WriteStoreHeader(encoder);
|
|
byte[] encoded = encoder.CloseAndReturnBuffer()!;
|
|
|
|
using var decoder = new BinaryDecoder(encoded, m_messageContext);
|
|
|
|
Assert.That(
|
|
() => SubscriptionStore.ValidateStoreHeader(decoder),
|
|
Throws.Nothing);
|
|
}
|
|
|
|
[Test]
|
|
public void StoreHeaderRejectsLegacyUnsafeFormat()
|
|
{
|
|
using var encoder = new BinaryEncoder(m_messageContext);
|
|
encoder.WriteStringArray(null, ["http://opcfoundation.org/UA/"]);
|
|
byte[] encoded = encoder.CloseAndReturnBuffer()!;
|
|
|
|
using var decoder = new BinaryDecoder(encoded, m_messageContext);
|
|
|
|
Assert.That(
|
|
() => SubscriptionStore.ValidateStoreHeader(decoder),
|
|
Throws.TypeOf<InvalidDataException>());
|
|
}
|
|
|
|
private ServiceMessageContext m_messageContext = null!;
|
|
private DefaultTelemetry m_telemetry = null!;
|
|
}
|
|
}
|