Files
UA-.NETStandard/tests/Opc.Ua.Client.Tests/Session/X509ClientIdentityProviderSelectionTests.cs

778 lines
32 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.Collections.Generic;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
using Opc.Ua.Identity;
using Opc.Ua.Security.Certificates;
using Opc.Ua.Tests;
namespace Opc.Ua.Client.Tests.Identity
{
[TestFixture]
[Category("Identity")]
public sealed class X509ClientIdentityProviderSelectionTests
{
[Test]
public async Task RsaCertSelectsRsaPolicyOverEccPolicy()
{
using Certificate cert = CreateRsaCertificate();
X509ClientIdentityProvider provider = CreateProvider(cert);
UserTokenPolicy ecc = CreatePolicy("ecc", SecurityPolicies.ECC_nistP256);
UserTokenPolicy rsa = CreatePolicy("rsa", SecurityPolicies.Basic256Sha256);
EndpointDescription endpoint = CreateEndpoint(
SecurityPolicies.Basic256Sha256,
ecc,
rsa);
UserTokenPolicy selected = await provider
.SelectUserTokenPolicyAsync(CreateContextWithAllPolicies(endpoint))
.ConfigureAwait(false);
Assert.That(selected.PolicyId, Is.EqualTo("rsa"));
}
[Test]
public async Task NistP256CertSelectsNistP256PolicyOverNistP384()
{
using Certificate cert = CreateEccCertificate(ECCurve.NamedCurves.nistP256);
X509ClientIdentityProvider provider = CreateProvider(cert);
UserTokenPolicy nist384 = CreatePolicy("p384", SecurityPolicies.ECC_nistP384);
UserTokenPolicy nist256 = CreatePolicy("p256", SecurityPolicies.ECC_nistP256);
UserTokenPolicy rsa = CreatePolicy("rsa", SecurityPolicies.Basic256Sha256);
EndpointDescription endpoint = CreateEndpoint(
SecurityPolicies.ECC_nistP256,
nist384,
nist256,
rsa);
UserTokenPolicy selected = await provider
.SelectUserTokenPolicyAsync(CreateContextWithAllPolicies(endpoint))
.ConfigureAwait(false);
Assert.That(selected.PolicyId, Is.EqualTo("p256"));
}
[Test]
public void NistP384CertWithOnlyNistP256OfferedThrowsBadIdentityTokenRejected()
{
using Certificate cert = CreateEccCertificate(ECCurve.NamedCurves.nistP384);
X509ClientIdentityProvider provider = CreateProvider(cert);
UserTokenPolicy nist256 = CreatePolicy("p256", SecurityPolicies.ECC_nistP256);
EndpointDescription endpoint = CreateEndpoint(
SecurityPolicies.ECC_nistP256,
nist256);
ServiceResultException ex = Assert.ThrowsAsync<ServiceResultException>(
async () => await provider
.SelectUserTokenPolicyAsync(CreateContextWithAllPolicies(endpoint))
.ConfigureAwait(false));
Assert.That(ex.StatusCode, Is.EqualTo(StatusCodes.BadIdentityTokenRejected));
Assert.That(ex.Message, Does.Contain("CertificateAlgorithmMismatch"));
Assert.That(ex.Message, Does.Contain("NistP384"));
Assert.That(ex.Message, Does.Contain("NistP256"));
}
[Test]
public void DisabledPolicyIsRejectedWithReasonInDiagnostic()
{
using Certificate cert = CreateRsaCertificate();
X509ClientIdentityProvider provider = CreateProvider(cert);
UserTokenPolicy basic128 = CreatePolicy("basic128", SecurityPolicies.Basic128Rsa15);
EndpointDescription endpoint = CreateEndpoint(
SecurityPolicies.Basic256Sha256,
basic128);
var context = new IdentitySelectionContext(
endpoint,
endpoint.UserIdentityTokens,
ServiceMessageContext.CreateEmpty(NUnitTelemetryContext.Create()),
new[] { SecurityPolicies.Basic256Sha256 });
ServiceResultException ex = Assert.ThrowsAsync<ServiceResultException>(
async () => await provider
.SelectUserTokenPolicyAsync(context)
.ConfigureAwait(false));
Assert.That(ex.StatusCode, Is.EqualTo(StatusCodes.BadIdentityTokenRejected));
Assert.That(ex.Message, Does.Contain("NotEnabledByClient"));
Assert.That(ex.Message, Does.Contain(SecurityPolicies.Basic128Rsa15));
Assert.That(ex.Message, Does.Contain("basic128"));
}
[Test]
public async Task EnabledFilterPicksEnabledPolicyOverDisabledMatch()
{
using Certificate cert = CreateRsaCertificate();
X509ClientIdentityProvider provider = CreateProvider(cert);
UserTokenPolicy basic128 = CreatePolicy("basic128", SecurityPolicies.Basic128Rsa15);
UserTokenPolicy basic256 = CreatePolicy("basic256", SecurityPolicies.Basic256Sha256);
EndpointDescription endpoint = CreateEndpoint(
SecurityPolicies.Basic256Sha256,
basic128,
basic256);
var context = new IdentitySelectionContext(
endpoint,
endpoint.UserIdentityTokens,
ServiceMessageContext.CreateEmpty(NUnitTelemetryContext.Create()),
new[] { SecurityPolicies.Basic256Sha256 });
UserTokenPolicy selected = await provider
.SelectUserTokenPolicyAsync(context)
.ConfigureAwait(false);
Assert.That(selected.PolicyId, Is.EqualTo("basic256"));
}
[Test]
public void CertificateLoadFailureSurfacesAsCertificateLoadFailedDiagnostic()
{
var certId = new CertificateIdentifier
{
Thumbprint = "0000000000000000000000000000000000000000",
SubjectName = "CN=Missing"
};
var provider = new X509ClientIdentityProvider(
certId,
new FakeCertificatePasswordProvider(),
new EmptyCertificateProvider());
UserTokenPolicy policy = CreatePolicy("user", SecurityPolicies.Basic256Sha256);
EndpointDescription endpoint = CreateEndpoint(
SecurityPolicies.Basic256Sha256,
policy);
ServiceResultException ex = Assert.ThrowsAsync<ServiceResultException>(
async () => await provider
.SelectUserTokenPolicyAsync(CreateContextWithAllPolicies(endpoint))
.ConfigureAwait(false));
Assert.That(ex.StatusCode, Is.EqualTo(StatusCodes.BadIdentityTokenRejected));
Assert.That(ex.Message, Does.Contain("CertificateLoadFailed"));
Assert.That(ex.Message, Does.Contain("CN=Missing"));
}
[Test]
public async Task UnsupportedTokenTypeIncludedInDiagnostic()
{
UserTokenPolicy anon = CreatePolicy("anon", SecurityPolicies.None, UserTokenType.Anonymous);
EndpointDescription endpoint = CreateEndpoint(SecurityPolicies.None, anon);
var provider = new UserNamePasswordIdentityProvider(
"user1",
new FakeSecretRegistry(),
new SecretIdentifier("password", "fake"));
ServiceResultException ex = Assert.ThrowsAsync<ServiceResultException>(
async () => await provider
.SelectUserTokenPolicyAsync(CreateContextWithAllPolicies(endpoint))
.ConfigureAwait(false));
Assert.That(ex.StatusCode, Is.EqualTo(StatusCodes.BadIdentityTokenRejected));
Assert.That(ex.Message, Does.Contain("TokenTypeNotSupported"));
Assert.That(ex.Message, Does.Contain("anon"));
await Task.CompletedTask.ConfigureAwait(false);
}
[Test]
public void Rsa1024CertWithBasic256Sha256TriggersKeyLengthMismatch()
{
using Certificate cert = CreateRsaCertificate(keySize: 1024);
X509ClientIdentityProvider provider = CreateProvider(cert);
UserTokenPolicy basic256 = CreatePolicy("basic256", SecurityPolicies.Basic256Sha256);
EndpointDescription endpoint = CreateEndpoint(SecurityPolicies.Basic256Sha256, basic256);
ServiceResultException ex = Assert.ThrowsAsync<ServiceResultException>(
async () => await provider
.SelectUserTokenPolicyAsync(CreateContextWithAllPolicies(endpoint))
.ConfigureAwait(false));
Assert.That(ex.StatusCode, Is.EqualTo(StatusCodes.BadIdentityTokenRejected));
Assert.That(ex.Message, Does.Contain("CertificateKeyLengthMismatch"));
Assert.That(ex.Message, Does.Contain("1024-bit"));
}
[Test]
public void EccUserNamePolicyWithRsaInstanceCertTriggersInstanceMismatch()
{
// The instance-cert gate fires for UserName/IssuedToken
// (EccEncryptedSecret encryption) — not X509 / Anonymous.
var provider = new UserNamePasswordIdentityProvider(
"user1",
new FakeSecretRegistry(),
new SecretIdentifier("password", "fake"));
UserTokenPolicy ecc = CreatePolicy(
"ecc",
SecurityPolicies.ECC_nistP256,
UserTokenType.UserName);
EndpointDescription endpoint = CreateEndpoint(SecurityPolicies.ECC_nistP256, ecc);
IdentitySelectionContext context = CreateContextWithAllPolicies(endpoint) with
{
ClientInstanceCertificateAlgorithm = CertificateKeyAlgorithm.RSA,
ClientInstanceCertificateKeySize = 2048
};
ServiceResultException ex = Assert.ThrowsAsync<ServiceResultException>(
async () => await provider
.SelectUserTokenPolicyAsync(context)
.ConfigureAwait(false));
Assert.That(ex.StatusCode, Is.EqualTo(StatusCodes.BadIdentityTokenRejected));
Assert.That(ex.Message, Does.Contain("ClientInstanceCertificateAlgorithmMismatch"));
Assert.That(ex.Message, Does.Contain("RSA"));
Assert.That(ex.Message, Does.Contain("NistP256"));
}
[Test]
public void EccNistP256InstanceCertRejectsNistP384UserNamePolicy()
{
var provider = new UserNamePasswordIdentityProvider(
"user1",
new FakeSecretRegistry(),
new SecretIdentifier("password", "fake"));
UserTokenPolicy ecc = CreatePolicy(
"p384",
SecurityPolicies.ECC_nistP384,
UserTokenType.UserName);
EndpointDescription endpoint = CreateEndpoint(SecurityPolicies.ECC_nistP384, ecc);
IdentitySelectionContext context = CreateContextWithAllPolicies(endpoint) with
{
ClientInstanceCertificateAlgorithm = CertificateKeyAlgorithm.NistP256
};
ServiceResultException ex = Assert.ThrowsAsync<ServiceResultException>(
async () => await provider
.SelectUserTokenPolicyAsync(context)
.ConfigureAwait(false));
Assert.That(ex.Message, Does.Contain("ClientInstanceCertificateAlgorithmMismatch"));
Assert.That(ex.Message, Does.Contain("NistP256"));
Assert.That(ex.Message, Does.Contain("NistP384"));
}
[Test]
public async Task EccNistP256InstanceCertSelectsMatchingUserNamePolicy()
{
var provider = new UserNamePasswordIdentityProvider(
"user1",
new FakeSecretRegistry(),
new SecretIdentifier("password", "fake"));
UserTokenPolicy ecc = CreatePolicy(
"p256",
SecurityPolicies.ECC_nistP256,
UserTokenType.UserName);
EndpointDescription endpoint = CreateEndpoint(SecurityPolicies.ECC_nistP256, ecc);
IdentitySelectionContext context = CreateContextWithAllPolicies(endpoint) with
{
ClientInstanceCertificateAlgorithm = CertificateKeyAlgorithm.NistP256
};
UserTokenPolicy selected = await provider
.SelectUserTokenPolicyAsync(context)
.ConfigureAwait(false);
Assert.That(selected.PolicyId, Is.EqualTo("p256"));
}
[Test]
public async Task X509UserTokenIsExemptFromInstanceCertCurveGate()
{
// X509IdentityTokenHandler.EncryptAsync is a no-op (the
// token carries a DER cert + signature using the user
// cert's private key); the instance-cert curve is
// irrelevant, so the gate must NOT block this flow.
using Certificate userCert = CreateEccCertificate(ECCurve.NamedCurves.nistP256);
X509ClientIdentityProvider provider = CreateProvider(userCert);
UserTokenPolicy ecc = CreatePolicy("ecc", SecurityPolicies.ECC_nistP256);
EndpointDescription endpoint = CreateEndpoint(SecurityPolicies.ECC_nistP256, ecc);
IdentitySelectionContext context = CreateContextWithAllPolicies(endpoint) with
{
ClientInstanceCertificateAlgorithm = CertificateKeyAlgorithm.RSA,
ClientInstanceCertificateKeySize = 2048
};
UserTokenPolicy selected = await provider
.SelectUserTokenPolicyAsync(context)
.ConfigureAwait(false);
Assert.That(selected.PolicyId, Is.EqualTo("ecc"));
}
[Test]
public async Task AnonymousTokenIsExemptFromInstanceCertCurveGate()
{
var provider = new AnonymousIdentityProvider();
UserTokenPolicy anon = CreatePolicy(
"anon",
SecurityPolicies.ECC_nistP256,
UserTokenType.Anonymous);
EndpointDescription endpoint = CreateEndpoint(SecurityPolicies.ECC_nistP256, anon);
IdentitySelectionContext context = CreateContextWithAllPolicies(endpoint) with
{
ClientInstanceCertificateAlgorithm = CertificateKeyAlgorithm.RSA,
ClientInstanceCertificateKeySize = 2048
};
UserTokenPolicy selected = await provider
.SelectUserTokenPolicyAsync(context)
.ConfigureAwait(false);
Assert.That(selected.PolicyId, Is.EqualTo("anon"));
}
[Test]
public void BoundEphemeralKeyPinsSelectionAndNamesRenewApi()
{
using Certificate userCert = CreateEccCertificate(ECCurve.NamedCurves.nistP256);
X509ClientIdentityProvider provider = CreateProvider(userCert);
UserTokenPolicy p384 = CreatePolicy("p384", SecurityPolicies.ECC_nistP384);
EndpointDescription endpoint = CreateEndpoint(SecurityPolicies.ECC_nistP384, p384);
IdentitySelectionContext context = CreateContextWithAllPolicies(endpoint) with
{
ClientInstanceCertificateAlgorithm = CertificateKeyAlgorithm.NistP384,
CurrentEphemeralKeyPolicyUri = SecurityPolicies.ECC_nistP256
};
ServiceResultException ex = Assert.ThrowsAsync<ServiceResultException>(
async () => await provider
.SelectUserTokenPolicyAsync(context)
.ConfigureAwait(false));
Assert.That(ex.Message, Does.Contain("EphemeralKeyPolicyMismatch"));
Assert.That(ex.Message, Does.Contain(SecurityPolicies.ECC_nistP256));
Assert.That(ex.Message, Does.Contain("UpdateIdentityAsync"));
Assert.That(ex.Message, Does.Contain("overrideUserTokenPolicyUri"));
}
[Test]
public async Task BoundEphemeralKeyAllowsMatchingPolicy()
{
using Certificate userCert = CreateEccCertificate(ECCurve.NamedCurves.nistP256);
X509ClientIdentityProvider provider = CreateProvider(userCert);
UserTokenPolicy p256 = CreatePolicy("p256", SecurityPolicies.ECC_nistP256);
UserTokenPolicy p384 = CreatePolicy("p384", SecurityPolicies.ECC_nistP384);
EndpointDescription endpoint = CreateEndpoint(SecurityPolicies.ECC_nistP256, p384, p256);
IdentitySelectionContext context = CreateContextWithAllPolicies(endpoint) with
{
ClientInstanceCertificateAlgorithm = CertificateKeyAlgorithm.NistP256,
CurrentEphemeralKeyPolicyUri = SecurityPolicies.ECC_nistP256
};
UserTokenPolicy selected = await provider
.SelectUserTokenPolicyAsync(context)
.ConfigureAwait(false);
Assert.That(selected.PolicyId, Is.EqualTo("p256"));
}
[Test]
public void NoneChannelRejectsEccUserNameTokenPerPart4_7_41()
{
// Part 4 §7.41: when SecurityMode is None, USERNAME and
// ISSUEDTOKEN UserTokenPolicies must not use ECC/RSA-DH
// SecurityPolicies.
var provider = new UserNamePasswordIdentityProvider(
"user1",
new FakeSecretRegistry(),
new SecretIdentifier("password", "fake"));
UserTokenPolicy ecc = CreatePolicy(
"ecc",
SecurityPolicies.ECC_nistP256,
UserTokenType.UserName);
EndpointDescription endpoint = CreateEndpoint(SecurityPolicies.None, ecc);
ServiceResultException ex = Assert.ThrowsAsync<ServiceResultException>(
async () => await provider
.SelectUserTokenPolicyAsync(CreateContextWithAllPolicies(endpoint))
.ConfigureAwait(false));
Assert.That(ex.StatusCode, Is.EqualTo(StatusCodes.BadIdentityTokenRejected));
Assert.That(ex.Message, Does.Contain("InsecureChannelEccUserTokenForbidden"));
Assert.That(ex.Message, Does.Contain(SecurityPolicies.ECC_nistP256));
}
[Test]
public void RsaChannelRejectsEccUserNameTokenWithMismatchedFamily()
{
// Channel uses RSA (Basic256Sha256), user-token policy uses
// ECC — Part 4 §7.41: SHALL use the same PublicKey
// algorithm as the SecureChannel.
var provider = new UserNamePasswordIdentityProvider(
"user1",
new FakeSecretRegistry(),
new SecretIdentifier("password", "fake"));
UserTokenPolicy ecc = CreatePolicy(
"ecc",
SecurityPolicies.ECC_nistP256,
UserTokenType.UserName);
EndpointDescription endpoint = CreateEndpoint(SecurityPolicies.Basic256Sha256, ecc);
ServiceResultException ex = Assert.ThrowsAsync<ServiceResultException>(
async () => await provider
.SelectUserTokenPolicyAsync(CreateContextWithAllPolicies(endpoint))
.ConfigureAwait(false));
Assert.That(ex.Message, Does.Contain("PublicKeyAlgorithmMismatch"));
Assert.That(ex.Message, Does.Contain(SecurityPolicies.Basic256Sha256));
Assert.That(ex.Message, Does.Contain(SecurityPolicies.ECC_nistP256));
}
[Test]
public void EccChannelRejectsRsaUserNameTokenWithMismatchedFamily()
{
// Reverse direction: ECC channel + RSA user-token policy.
var provider = new UserNamePasswordIdentityProvider(
"user1",
new FakeSecretRegistry(),
new SecretIdentifier("password", "fake"));
UserTokenPolicy rsa = CreatePolicy(
"rsa",
SecurityPolicies.Basic256Sha256,
UserTokenType.UserName);
EndpointDescription endpoint = CreateEndpoint(SecurityPolicies.ECC_nistP256, rsa);
IdentitySelectionContext context = CreateContextWithAllPolicies(endpoint) with
{
ClientInstanceCertificateAlgorithm = CertificateKeyAlgorithm.NistP256
};
ServiceResultException ex = Assert.ThrowsAsync<ServiceResultException>(
async () => await provider
.SelectUserTokenPolicyAsync(context)
.ConfigureAwait(false));
Assert.That(ex.Message, Does.Contain("PublicKeyAlgorithmMismatch"));
}
[Test]
public async Task RsaChannelAcceptsEmptyUserNameSecurityPolicyUri()
{
// §7.41: empty SecurityPolicyUri inherits the channel — OK.
var provider = new UserNamePasswordIdentityProvider(
"user1",
new FakeSecretRegistry(),
new SecretIdentifier("password", "fake"));
UserTokenPolicy inherit = new()
{
PolicyId = "inherit",
TokenType = UserTokenType.UserName,
SecurityPolicyUri = null
};
EndpointDescription endpoint = CreateEndpoint(SecurityPolicies.Basic256Sha256, inherit);
UserTokenPolicy selected = await provider
.SelectUserTokenPolicyAsync(CreateContextWithAllPolicies(endpoint))
.ConfigureAwait(false);
Assert.That(selected.PolicyId, Is.EqualTo("inherit"));
}
[Test]
public async Task NoneChannelAcceptsCertificateTokenWithEccPolicyPerSpecExemption()
{
// §7.41 last paragraph: CERTIFICATE tokens may use any
// SecurityPolicy regardless of channel mode.
using Certificate userCert = CreateEccCertificate(ECCurve.NamedCurves.nistP256);
X509ClientIdentityProvider provider = CreateProvider(userCert);
UserTokenPolicy cert = CreatePolicy("ecc-cert", SecurityPolicies.ECC_nistP256);
EndpointDescription endpoint = CreateEndpoint(SecurityPolicies.None, cert);
UserTokenPolicy selected = await provider
.SelectUserTokenPolicyAsync(CreateContextWithAllPolicies(endpoint))
.ConfigureAwait(false);
Assert.That(selected.PolicyId, Is.EqualTo("ecc-cert"));
}
[Test]
public async Task RsaChannelAcceptsCertificateTokenWithEccPolicyPerSpecExemption()
{
// Channel RSA + Certificate user-token policy ECC — exempt
// from the same-PublicKey-algorithm rule.
using Certificate userCert = CreateEccCertificate(ECCurve.NamedCurves.nistP256);
X509ClientIdentityProvider provider = CreateProvider(userCert);
UserTokenPolicy cert = CreatePolicy("ecc-cert", SecurityPolicies.ECC_nistP256);
EndpointDescription endpoint = CreateEndpoint(SecurityPolicies.Basic256Sha256, cert);
UserTokenPolicy selected = await provider
.SelectUserTokenPolicyAsync(CreateContextWithAllPolicies(endpoint))
.ConfigureAwait(false);
Assert.That(selected.PolicyId, Is.EqualTo("ecc-cert"));
}
[Test]
public void DiagnosticTruncatesLongStringsAndCapsRejectedPolicyCount()
{
// Build an endpoint with many offered policies and very long
// server-supplied strings to exercise the bounded-diagnostic
// hardening (CR 7.1 / CR/SR 3.7).
var provider = new UserNamePasswordIdentityProvider(
"user1",
new FakeSecretRegistry(),
new SecretIdentifier("password", "fake"));
string longString = new('A', 4096);
var policies = new List<UserTokenPolicy>();
for (int i = 0; i < 50; i++)
{
policies.Add(new UserTokenPolicy
{
PolicyId = longString + i,
TokenType = UserTokenType.Certificate,
SecurityPolicyUri = longString
});
}
var endpoint = new EndpointDescription
{
SecurityPolicyUri = SecurityPolicies.None,
UserIdentityTokens = [.. policies]
};
var context = new IdentitySelectionContext(
endpoint,
endpoint.UserIdentityTokens,
ServiceMessageContext.CreateEmpty(NUnitTelemetryContext.Create()));
ServiceResultException ex = Assert.ThrowsAsync<ServiceResultException>(
async () => await provider
.SelectUserTokenPolicyAsync(context)
.ConfigureAwait(false));
Assert.That(ex.StatusCode, Is.EqualTo(StatusCodes.BadIdentityTokenRejected));
Assert.That(ex.Message, Does.Contain("…(truncated)"));
Assert.That(ex.Message, Does.Contain("more rejected policies"));
// Diagnostic must not contain the full 4 KiB string verbatim
// (we truncate at 256 chars).
Assert.That(ex.Message, Does.Not.Contain(longString));
// And must not contain every single PolicyId (cap is 32).
Assert.That(ex.Message, Does.Not.Contain(longString + "49"));
}
private static X509ClientIdentityProvider CreateProvider(Certificate cert)
{
var certId = new CertificateIdentifier
{
Thumbprint = cert.Thumbprint,
SubjectName = cert.Subject
};
return new X509ClientIdentityProvider(
certId,
new FakeCertificatePasswordProvider(),
new InMemoryCertificateProvider(cert));
}
private static Certificate CreateRsaCertificate(ushort keySize = 0)
{
ICertificateBuilder builder = CertificateBuilder.Create("CN=User")
.SetNotBefore(DateTime.UtcNow.AddMinutes(-5))
.SetLifeTime(TimeSpan.FromDays(1));
if (keySize != 0)
{
return builder.SetRSAKeySize(keySize).CreateForRSA();
}
return builder.CreateForRSA();
}
private static Certificate CreateEccCertificate(ECCurve curve)
{
return CertificateBuilder.Create("CN=User")
.SetNotBefore(DateTime.UtcNow.AddMinutes(-5))
.SetLifeTime(TimeSpan.FromDays(1))
.SetECCurve(curve)
.CreateForECDsa();
}
private static UserTokenPolicy CreatePolicy(
string id,
string securityPolicyUri,
UserTokenType tokenType = UserTokenType.Certificate)
{
return new UserTokenPolicy
{
PolicyId = id,
TokenType = tokenType,
SecurityPolicyUri = securityPolicyUri
};
}
private static EndpointDescription CreateEndpoint(
string securityPolicyUri,
params UserTokenPolicy[] policies)
{
return new EndpointDescription
{
SecurityPolicyUri = securityPolicyUri,
UserIdentityTokens = [.. policies]
};
}
private static IdentitySelectionContext CreateContextWithAllPolicies(EndpointDescription endpoint)
{
// Allows every offered SecurityPolicyUri so the curve / token-type filter is the only gate.
var enabled = new HashSet<string>(StringComparer.Ordinal);
if (!string.IsNullOrEmpty(endpoint.SecurityPolicyUri))
{
enabled.Add(endpoint.SecurityPolicyUri);
}
foreach (UserTokenPolicy policy in endpoint.UserIdentityTokens)
{
if (!string.IsNullOrEmpty(policy.SecurityPolicyUri))
{
enabled.Add(policy.SecurityPolicyUri);
}
}
string[] enabledUris = new string[enabled.Count];
enabled.CopyTo(enabledUris);
return new IdentitySelectionContext(
endpoint,
endpoint.UserIdentityTokens,
ServiceMessageContext.CreateEmpty(NUnitTelemetryContext.Create()),
enabledUris);
}
private sealed class FakeCertificatePasswordProvider : ICertificatePasswordProvider
{
public char[] GetPassword(CertificateIdentifier certificateIdentifier)
{
return [];
}
}
private sealed class InMemoryCertificateProvider : ICertificateProvider
{
private readonly Certificate m_certificate;
public InMemoryCertificateProvider(Certificate certificate)
{
m_certificate = certificate ?? throw new ArgumentNullException(nameof(certificate));
}
public Certificate? TryGetPrivateKeyCertificate(string thumbprint)
{
if (string.IsNullOrEmpty(thumbprint) ||
!StringComparer.OrdinalIgnoreCase.Equals(thumbprint, m_certificate.Thumbprint))
{
return null;
}
return m_certificate.AddRef();
}
public ValueTask<Certificate?> GetPrivateKeyCertificateAsync(
CertificateIdentifier identifier,
ICertificatePasswordProvider? passwordProvider = null,
string? applicationUri = null,
CancellationToken ct = default)
{
return Matches(identifier)
? new ValueTask<Certificate?>(m_certificate.AddRef())
: new ValueTask<Certificate?>((Certificate?)null);
}
private bool Matches(CertificateIdentifier identifier)
{
if (identifier == null)
{
return false;
}
if (!string.IsNullOrEmpty(identifier.Thumbprint))
{
return StringComparer.OrdinalIgnoreCase.Equals(
identifier.Thumbprint,
m_certificate.Thumbprint);
}
return string.Equals(
identifier.SubjectName,
m_certificate.Subject,
StringComparison.Ordinal);
}
}
private sealed class EmptyCertificateProvider : ICertificateProvider
{
public Certificate? TryGetPrivateKeyCertificate(string thumbprint)
{
return null;
}
public ValueTask<Certificate?> GetPrivateKeyCertificateAsync(
CertificateIdentifier identifier,
ICertificatePasswordProvider? passwordProvider = null,
string? applicationUri = null,
CancellationToken ct = default)
{
return new ValueTask<Certificate?>((Certificate?)null);
}
}
private sealed class FakeSecretRegistry : ISecretRegistry
{
public void RegisterStore(ISecretStore store)
{
}
public ISecret? TryGet(SecretIdentifier id)
{
return null;
}
public ValueTask<ISecret?> GetAsync(
SecretIdentifier id,
CancellationToken ct = default)
{
return new ValueTask<ISecret?>((ISecret?)null);
}
}
}
}