mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-16 21:02:51 +08:00
Withhold ECC policies under FipsOnly on .NET Framework
The net48 raw ECDH agreement runs in BouncyCastle and bypasses the crypto provider registry, so the auditor could not see it and FipsOnly still permitted the NIST ECC policies. CryptoCompliance.IsPolicyPermitted now withholds every ECC policy on .NET Framework under FipsOnly. Docs and the bundled migration skill copy updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -66,7 +66,8 @@ SecureChannel and user-token policies (`ECC_nistP256`, `ECC_nistP384`,
|
||||
`ECC_brainpoolP256r1`, `ECC_brainpoolP384r1`) are available there.
|
||||
|
||||
**Migration:** nothing is required. Note that on .NET Framework the agreement
|
||||
step does not run in a platform-validated cryptographic module; deployments
|
||||
step does not run in a platform-validated cryptographic module, so
|
||||
`CryptoCompliancePolicy.FipsOnly` withholds the ECC policies there; deployments
|
||||
with FIPS or certified-module requirements should use the .NET 8+ build or an
|
||||
RSA policy. The `_AesGcm` and `_ChaChaPoly` ECC variants still need .NET 8 or
|
||||
later, and `SecurityPolicies.GetInfo` returns `null` for them on .NET Framework.
|
||||
|
||||
@@ -253,7 +253,9 @@ These algorithms are **not** FIPS-approved and are enabled by default:
|
||||
| SHA-1 and P-SHA1 (Basic128Rsa15, Basic256) | Deprecated for new signatures by SP 800-131A |
|
||||
|
||||
`net48` additionally uses the `BouncyCastle.Cryptography` package for elliptic curve
|
||||
certificate building. That package is **not** validated — the validated Bouncy Castle product is a
|
||||
certificate building and for the raw ECDH agreement of the ECC security policies. Because that
|
||||
agreement bypasses the provider registry, `FipsOnly` withholds every ECC policy on `net48`, including
|
||||
the NIST curves. That package is **not** validated — the validated Bouncy Castle product is a
|
||||
separate, commercially licensed distribution — so **that target framework cannot make a FIPS claim at
|
||||
all**.
|
||||
|
||||
|
||||
+2
-1
@@ -308,7 +308,8 @@ which `Opc.Ua.Security.Certificates` already references on that target.
|
||||
The ephemeral keys, ECDSA signatures and HKDF stay on the platform (CNG)
|
||||
providers; only the agreement step is managed code. Deployments bound to
|
||||
FIPS-validated or other certified cryptographic modules should note that
|
||||
this step is not performed by a validated module on .NET Framework; use the
|
||||
this step is not performed by a validated module on .NET Framework, so
|
||||
`CryptoCompliancePolicy.FipsOnly` withholds the ECC policies there; use the
|
||||
.NET 8+ build, or an RSA policy, where that matters. The .NET 8+ builds use
|
||||
`ECDiffieHellman.DeriveRawSecretAgreement` and do not load BouncyCastle for it.
|
||||
|
||||
|
||||
@@ -66,7 +66,8 @@ SecureChannel and user-token policies (`ECC_nistP256`, `ECC_nistP384`,
|
||||
`ECC_brainpoolP256r1`, `ECC_brainpoolP384r1`) are available there.
|
||||
|
||||
**Migration:** nothing is required. Note that on .NET Framework the agreement
|
||||
step does not run in a platform-validated cryptographic module; deployments
|
||||
step does not run in a platform-validated cryptographic module, so
|
||||
`CryptoCompliancePolicy.FipsOnly` withholds the ECC policies there; deployments
|
||||
with FIPS or certified-module requirements should use the .NET 8+ build or an
|
||||
RSA policy. The `_AesGcm` and `_ChaChaPoly` ECC variants still need .NET 8 or
|
||||
later, and `SecurityPolicies.GetInfo` returns `null` for them on .NET Framework.
|
||||
|
||||
@@ -77,7 +77,27 @@ namespace Opc.Ua
|
||||
SecurityPolicyInfo? info = SecurityPolicies.Default.GetInfoIgnoringPlatformSupport(
|
||||
securityPolicyUri ?? string.Empty);
|
||||
|
||||
return info != null && info.IsFipsApproved;
|
||||
return info != null && info.IsFipsApproved && !UsesUnvalidatedKeyAgreement(info);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether this build performs a step of the policy outside any
|
||||
/// validated module, whatever providers are registered.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The .NET Framework BCL cannot return the raw ECDH secret OPC UA
|
||||
/// requires, so that build computes the agreement in the managed
|
||||
/// BouncyCastle implementation. It bypasses the provider registry, so
|
||||
/// the auditor cannot see it, and the ECC policies are withheld instead.
|
||||
/// </remarks>
|
||||
private static bool UsesUnvalidatedKeyAgreement(SecurityPolicyInfo info)
|
||||
{
|
||||
#if NETFRAMEWORK
|
||||
return info.CertificateKeyFamily == CertificateKeyFamily.ECC;
|
||||
#else
|
||||
_ = info;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -280,10 +280,12 @@ namespace Opc.Ua.Core.Tests.Security.Crypto
|
||||
[TestCase(SecurityPolicies.Basic256Sha256)]
|
||||
[TestCase(SecurityPolicies.Aes128_Sha256_RsaOaep)]
|
||||
[TestCase(SecurityPolicies.Aes256_Sha256_RsaPss)]
|
||||
[TestCase(SecurityPolicies.RSA_DH_AesGcm)]
|
||||
#if !NETFRAMEWORK
|
||||
[TestCase(SecurityPolicies.ECC_nistP256)]
|
||||
[TestCase(SecurityPolicies.ECC_nistP384)]
|
||||
[TestCase(SecurityPolicies.ECC_nistP256_AesGcm)]
|
||||
[TestCase(SecurityPolicies.RSA_DH_AesGcm)]
|
||||
#endif
|
||||
public void FipsOnlyPermitsApprovedPolicies(string securityPolicyUri)
|
||||
{
|
||||
Assert.That(
|
||||
@@ -292,6 +294,28 @@ namespace Opc.Ua.Core.Tests.Security.Crypto
|
||||
Is.True);
|
||||
}
|
||||
|
||||
#if NETFRAMEWORK
|
||||
/// <summary>
|
||||
/// On .NET Framework the ECDH agreement runs in BouncyCastle, outside
|
||||
/// any validated module, so even the NIST ECC policies are withheld.
|
||||
/// </summary>
|
||||
[Test]
|
||||
[TestCase(SecurityPolicies.ECC_nistP256)]
|
||||
[TestCase(SecurityPolicies.ECC_nistP384)]
|
||||
[TestCase(SecurityPolicies.ECC_nistP256_AesGcm)]
|
||||
[TestCase(SecurityPolicies.ECC_nistP384_AesGcm)]
|
||||
public void FipsOnlyWithholdsEccPoliciesOnNetFramework(string securityPolicyUri)
|
||||
{
|
||||
SecurityPolicyInfo? info = SecurityPolicies.Default.GetInfoIgnoringPlatformSupport(
|
||||
securityPolicyUri);
|
||||
Assert.That(info?.IsFipsApproved, Is.True);
|
||||
Assert.That(
|
||||
CryptoCompliance.IsPolicyPermitted(
|
||||
securityPolicyUri, CryptoCompliancePolicy.FipsOnly),
|
||||
Is.False);
|
||||
}
|
||||
#endif
|
||||
|
||||
[Test]
|
||||
[TestCase(SecurityPolicies.Basic128Rsa15)]
|
||||
[TestCase(SecurityPolicies.Basic256)]
|
||||
@@ -324,12 +348,18 @@ namespace Opc.Ua.Core.Tests.Security.Crypto
|
||||
ArrayOf<string> filtered = CryptoCompliance.FilterPolicies(
|
||||
policies, CryptoCompliancePolicy.FipsOnly);
|
||||
|
||||
#if NETFRAMEWORK
|
||||
// The ECDH agreement is not validated on .NET Framework.
|
||||
Assert.That(filtered.Count, Is.EqualTo(1));
|
||||
Assert.That(filtered[0], Is.EqualTo(SecurityPolicies.Basic256Sha256));
|
||||
#else
|
||||
Assert.That(filtered.Count, Is.EqualTo(2));
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(filtered[0], Is.EqualTo(SecurityPolicies.Basic256Sha256));
|
||||
Assert.That(filtered[1], Is.EqualTo(SecurityPolicies.ECC_nistP256));
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user