diff --git a/.github/actions/build/action.yml b/.github/actions/build/action.yml index ba678226512..ca8134e84a6 100644 --- a/.github/actions/build/action.yml +++ b/.github/actions/build/action.yml @@ -16,6 +16,11 @@ inputs: default: "ice.proj" type: string + msbuild_command: + description: "The msbuild command to use" + default: "msbuild /m" + type: string + build_cpp_and_python: description: "Build C++ and Python" type: choice @@ -67,6 +72,6 @@ runs: - name: Build working-directory: ${{ inputs.working_directory }} - run: msbuild /m ${{ inputs.build_flags }} ${{ inputs.msbuild_project }} + run: ${{ inputs.msbuild_command }} ${{ inputs.build_flags }} ${{ inputs.msbuild_project }} shell: powershell if: runner.os == 'Windows' diff --git a/.github/actions/setup-dotnet/action.yml b/.github/actions/setup-dotnet/action.yml index f8f41c2bf35..c5012e250bf 100644 --- a/.github/actions/setup-dotnet/action.yml +++ b/.github/actions/setup-dotnet/action.yml @@ -1,8 +1,20 @@ name: Setup .NET + +inputs: + include_net10: + description: "Include .NET 10" + default: "false" + runs: using: "composite" steps: - name: Setup .NET 8 - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: - dotnet-version: 8.x + dotnet-version: 8.0.x + + - name: Setup .NET 10 + if: inputs.include_net10 == 'true' + uses: actions/setup-dotnet@v5 + with: + dotnet-version: 10.0.x diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2e16d8d43ff..1d559684d1c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -110,6 +110,31 @@ jobs: build_cpp_and_python: true build_android_controller: true + # .NET 10.0 + - os: ubuntu-24.04 + config: ".NET10" + working_directory: "csharp" + net_target_framework: "net10.0" + test_flags: "--target-framework=net10.0" + build_cpp_and_python: true + + - os: windows-2025 + config: ".NET10" + working_directory: "csharp" + msbuild_command: "dotnet msbuild" + build_flags: "/p:Platform=x64" + msbuild_project: "msbuild/ice.proj" + net_target_framework: "net10.0" + test_flags: "--target-framework=net10.0" + build_cpp_and_python: true + + - os: macos-26 + config: ".NET10" + working_directory: "csharp" + net_target_framework: "net10.0" + test_flags: "--target-framework=net10.0" + build_cpp_and_python: true + runs-on: ${{ matrix.os }} steps: - name: Checkout repository @@ -133,6 +158,8 @@ jobs: - name: Setup .NET uses: ./.github/actions/setup-dotnet + with: + include_net10: ${{ matrix.net_target_framework == 'net10.0' }} - name: Setup Java uses: ./.github/actions/setup-java @@ -183,6 +210,12 @@ jobs: echo "EnableAnalysis=true" >> $env:GITHUB_ENV shell: pwsh + - name: .NET Target Framework + if: matrix.net_target_framework == 'net10.0' + run: | + echo "AppTargetFramework=net10.0" >> $env:GITHUB_ENV + shell: pwsh + - name: Build ${{ matrix.config }} on ${{ matrix.os }} uses: ./.github/actions/build timeout-minutes: 90 @@ -191,6 +224,7 @@ jobs: build_cpp_and_python: ${{ matrix.build_cpp_and_python || false }} build_android_controller: ${{ matrix.build_android_controller || false }} build_flags: ${{ matrix.build_flags || '' }} + msbuild_command: ${{ matrix.msbuild_command || 'msbuild /m' }} msbuild_project: ${{ matrix.msbuild_project || 'ice.proj' }} - name: Install testing dependencies from pip diff --git a/csharp/msbuild/CodeAnalysis.Src.globalconfig b/csharp/msbuild/CodeAnalysis.Src.globalconfig index 709ec43d110..73150f12d9d 100644 --- a/csharp/msbuild/CodeAnalysis.Src.globalconfig +++ b/csharp/msbuild/CodeAnalysis.Src.globalconfig @@ -1,9 +1,6 @@ global_level = 110 is_global = true -# CA1515: Because an application's API isn't typically referenced from outside the assembly, types can be made internal -dotnet_diagnostic.CA1515.severity = none - # CA1849: Call async methods when in an async method dotnet_diagnostic.CA1849.severity = none diff --git a/csharp/msbuild/CodeAnalysis.Tests.globalconfig b/csharp/msbuild/CodeAnalysis.Tests.globalconfig index 988fcc7114a..b407a608705 100644 --- a/csharp/msbuild/CodeAnalysis.Tests.globalconfig +++ b/csharp/msbuild/CodeAnalysis.Tests.globalconfig @@ -32,3 +32,6 @@ dotnet_diagnostic.CA1016.severity = none # CA2008: Do not create tasks without passing a TaskScheduler dotnet_diagnostic.CA2008.severity = none + +# CA1515: Because an application's API isn't typically referenced from outside the assembly, types can be made internal +dotnet_diagnostic.CA1515.severity = none diff --git a/csharp/src/Ice/SSL/SSLEngine.cs b/csharp/src/Ice/SSL/SSLEngine.cs index 5ee3e7504dd..43d36222ae7 100644 --- a/csharp/src/Ice/SSL/SSLEngine.cs +++ b/csharp/src/Ice/SSL/SSLEngine.cs @@ -175,9 +175,7 @@ internal void traceStream(SslStream stream, string connInfo) s.Append("\nencrypted = " + (stream.IsEncrypted ? "yes" : "no")); s.Append("\nsigned = " + (stream.IsSigned ? "yes" : "no")); s.Append("\nmutually authenticated = " + (stream.IsMutuallyAuthenticated ? "yes" : "no")); - s.Append("\nhash algorithm = " + stream.HashAlgorithm + "/" + stream.HashStrength); - s.Append("\ncipher algorithm = " + stream.CipherAlgorithm + "/" + stream.CipherStrength); - s.Append("\nkey exchange algorithm = " + stream.KeyExchangeAlgorithm + "/" + stream.KeyExchangeStrength); + s.Append("\ncipher = " + stream.NegotiatedCipherSuite); s.Append("\nprotocol = " + stream.SslProtocol); _logger.trace(_securityTraceCategory, s.ToString()); } @@ -337,7 +335,7 @@ private static X509Certificate2Collection findCertificates( { try { - store = new X509Store((StoreName)Enum.Parse(typeof(StoreName), name, true), storeLocation); + store = new X509Store(Enum.Parse(name, true), storeLocation); } catch (ArgumentException) { diff --git a/csharp/src/Ice/SSL/TransceiverI.cs b/csharp/src/Ice/SSL/TransceiverI.cs index 9aafed2f123..81972f310d0 100644 --- a/csharp/src/Ice/SSL/TransceiverI.cs +++ b/csharp/src/Ice/SSL/TransceiverI.cs @@ -53,7 +53,7 @@ public int initialize(Ice.Internal.Buffer readBuffer, Ice.Internal.Buffer writeB Debug.Assert(_sslStream.IsAuthenticated); _authenticated = true; - _cipher = _sslStream.CipherAlgorithm.ToString(); + _cipher = _sslStream.NegotiatedCipherSuite.ToString(); _instance.verifyPeer((ConnectionInfo)getInfo(_incoming, _adapterName, connectionId: ""), ToString()); if (_instance.securityTraceLevel() >= 1) @@ -422,7 +422,7 @@ private void finishAuthenticate() // If authentication fails the task throws AuthenticationException. _writeResult.Wait(); _verified = true; - _cipher = _sslStream.CipherAlgorithm.ToString(); + _cipher = _sslStream.NegotiatedCipherSuite.ToString(); } catch (AggregateException ex) { diff --git a/csharp/src/Ice/UtilInternal/StringUtil.cs b/csharp/src/Ice/UtilInternal/StringUtil.cs index 17bb3d644e4..e622ad52358 100644 --- a/csharp/src/Ice/UtilInternal/StringUtil.cs +++ b/csharp/src/Ice/UtilInternal/StringUtil.cs @@ -693,9 +693,7 @@ public static bool match(string s, string pat, bool emptyMatch) // // Make sure end of the strings match // - if (!s[endIndex..].Equals( - pat.Substring(beginIndex + 1, pat.Length - beginIndex - 1), - StringComparison.Ordinal)) + if (!s[endIndex..].Equals(pat[(beginIndex + 1)..], StringComparison.Ordinal)) { return false; } diff --git a/csharp/src/iceboxnet/Server.cs b/csharp/src/iceboxnet/Server.cs index 30960e7373b..df7fb4a38f9 100644 --- a/csharp/src/iceboxnet/Server.cs +++ b/csharp/src/iceboxnet/Server.cs @@ -2,7 +2,7 @@ namespace IceBox; -public static class Server +internal static class Server { private static void usage() { diff --git a/csharp/test/IceSSL/configuration/AllTests.cs b/csharp/test/IceSSL/configuration/AllTests.cs index 24e96c7f61b..858939200fe 100644 --- a/csharp/test/IceSSL/configuration/AllTests.cs +++ b/csharp/test/IceSSL/configuration/AllTests.cs @@ -100,8 +100,8 @@ public static Test.ServerFactoryPrx allTests(Test.TestHelper helper, string defa // string caCert1File = defaultDir + "/ca1/ca1_cert.pem"; string caCert2File = defaultDir + "/ca2/ca2_cert.pem"; - using var caCert1 = new X509Certificate2(caCert1File); - using var caCert2 = new X509Certificate2(caCert2File); + using X509Certificate2 caCert1 = X509CertificateLoader.LoadCertificateFromFile(caCert1File); + using X509Certificate2 caCert2 = X509CertificateLoader.LoadCertificateFromFile(caCert2File); var store = new X509Store(StoreName.AuthRoot, StoreLocation.LocalMachine); bool isAdministrator = false; @@ -268,11 +268,14 @@ public static Test.ServerFactoryPrx allTests(Test.TestHelper helper, string defa ServerPrx server = fact.createServer(d); try { - using var clientCert = new X509Certificate2(defaultDir + "/ca1/client.p12", "password"); + using X509Certificate2 clientCert = + X509CertificateLoader.LoadPkcs12FromFile(defaultDir + "/ca1/client.p12", "password"); server.checkCert(clientCert.Subject, clientCert.Issuer); - using var serverCert = new X509Certificate2(defaultDir + "/ca1/server.p12", "password"); - using var caCert = new X509Certificate2(defaultDir + "/ca1/ca1_cert.pem"); + using X509Certificate2 serverCert = + X509CertificateLoader.LoadPkcs12FromFile(defaultDir + "/ca1/server.p12", "password"); + using X509Certificate2 caCert = + X509CertificateLoader.LoadCertificateFromFile(defaultDir + "/ca1/ca1_cert.pem"); var info = (Ice.SSL.ConnectionInfo)server.ice_getConnection().getInfo(); test(info.certs.Length == 1); @@ -295,7 +298,8 @@ public static Test.ServerFactoryPrx allTests(Test.TestHelper helper, string defa server = fact.createServer(d); try { - using var clientCert = new X509Certificate2(defaultDir + "/ca1/client.p12", "password"); + using X509Certificate2 clientCert = + X509CertificateLoader.LoadPkcs12FromFile(defaultDir + "/ca1/client.p12", "password"); server.checkCert(clientCert.Subject, clientCert.Issuer); } catch (Exception ex) @@ -1496,7 +1500,8 @@ public static Test.ServerFactoryPrx allTests(Test.TestHelper helper, string defa { foreach (string certPath in certificates) { - using var cert = new X509Certificate2(defaultDir + certPath, "password", storageFlags); + using X509Certificate2 cert = + X509CertificateLoader.LoadPkcs12FromFile(defaultDir + certPath, "password", storageFlags); certStore.Add(cert); } @@ -1558,7 +1563,8 @@ public static Test.ServerFactoryPrx allTests(Test.TestHelper helper, string defa { foreach (string certPath in certificates) { - using var cert = new X509Certificate2(defaultDir + certPath, "password"); + using X509Certificate2 cert = + X509CertificateLoader.LoadPkcs12FromFile(defaultDir + certPath, "password"); certStore.Remove(cert); } certStore.Close(); diff --git a/csharp/test/IceSSL/configuration/PlatformTests.cs b/csharp/test/IceSSL/configuration/PlatformTests.cs index 07fec89ac63..c3368977195 100644 --- a/csharp/test/IceSSL/configuration/PlatformTests.cs +++ b/csharp/test/IceSSL/configuration/PlatformTests.cs @@ -43,8 +43,8 @@ private static void clientValidatesServerUsingValidationCallback(TestHelper help { Console.Out.Write("client validates server certificate using validation callback... "); Console.Out.Flush(); - using var serverCertificate = - new X509Certificate2(Path.Combine(certificatesPath, "ca1/server.p12"), "password"); + using X509Certificate2 serverCertificate = + X509CertificateLoader.LoadPkcs12FromFile(Path.Combine(certificatesPath, "ca1", "server.p12"), "password"); var serverOptions = new SslServerAuthenticationOptions { ServerCertificate = serverCertificate, @@ -85,8 +85,8 @@ private static void clientRejectServerUsingValidationCallback(TestHelper helper, { Console.Out.Write("client rejects server certificate using validation callback... "); Console.Out.Flush(); - using var serverCertificate = - new X509Certificate2(Path.Combine(certificatesPath, "ca1/server.p12"), "password"); + using X509Certificate2 serverCertificate = + X509CertificateLoader.LoadPkcs12FromFile(Path.Combine(certificatesPath, "ca1", "server.p12"), "password"); var serverOptions = new SslServerAuthenticationOptions { ServerCertificate = serverCertificate, @@ -116,8 +116,8 @@ private static void clientRejectServerUsingDefaultValidationCallback(TestHelper { Console.Out.Write("client rejects server certificate using default validation callback... "); Console.Out.Flush(); - using var serverCertificate = - new X509Certificate2(Path.Combine(certificatesPath, "ca1/server.p12"), "password"); + using X509Certificate2 serverCertificate = + X509CertificateLoader.LoadPkcs12FromFile(Path.Combine(certificatesPath, "ca1", "server.p12"), "password"); var serverOptions = new SslServerAuthenticationOptions { ServerCertificate = serverCertificate, @@ -143,10 +143,10 @@ private static void serverValidatesClientUsingValidationCallback(TestHelper help { Console.Out.Write("server validates client certificate using validation callback... "); Console.Out.Flush(); - using var serverCertificate = - new X509Certificate2(Path.Combine(certificatesPath, "ca1/server.p12"), "password"); - using var clientCertificate = - new X509Certificate2(Path.Combine(certificatesPath, "ca1/client.p12"), "password"); + using X509Certificate2 serverCertificate = + X509CertificateLoader.LoadPkcs12FromFile(Path.Combine(certificatesPath, "ca1", "server.p12"), "password"); + using X509Certificate2 clientCertificate = + X509CertificateLoader.LoadPkcs12FromFile(Path.Combine(certificatesPath, "ca1", "client.p12"), "password"); var serverOptions = new SslServerAuthenticationOptions { ServerCertificate = serverCertificate, @@ -176,8 +176,10 @@ private static void serverRejectsClientUsingValidationCallback(TestHelper helper { Console.Out.Write("server rejects client certificate using validation callback... "); Console.Out.Flush(); - using var serverCertificate = new X509Certificate2(Path.Combine(certificatesPath, "ca1/server.p12"), "password"); - using var clientCertificate = new X509Certificate2(Path.Combine(certificatesPath, "ca1/client.p12"), "password"); + using X509Certificate2 serverCertificate = + X509CertificateLoader.LoadPkcs12FromFile(Path.Combine(certificatesPath, "ca1", "server.p12"), "password"); + using X509Certificate2 clientCertificate = + X509CertificateLoader.LoadPkcs12FromFile(Path.Combine(certificatesPath, "ca1", "client.p12"), "password"); var serverOptions = new SslServerAuthenticationOptions { ServerCertificate = serverCertificate, @@ -213,8 +215,10 @@ private static void serverRejectsClientUsingDefaultValidationCallback(TestHelper { Console.Out.Write("server rejects client certificate using default validation callback... "); Console.Out.Flush(); - using var serverCertificate = new X509Certificate2(Path.Combine(certificatesPath, "ca1/server.p12"), "password"); - using var clientCertificate = new X509Certificate2(Path.Combine(certificatesPath, "ca1/client.p12"), "password"); + using X509Certificate2 serverCertificate = + X509CertificateLoader.LoadPkcs12FromFile(Path.Combine(certificatesPath, "ca1", "server.p12"), "password"); + using X509Certificate2 clientCertificate = + X509CertificateLoader.LoadPkcs12FromFile(Path.Combine(certificatesPath, "ca1", "client.p12"), "password"); var serverOptions = new SslServerAuthenticationOptions { ServerCertificate = serverCertificate, @@ -250,14 +254,15 @@ private sealed class ServerState : IDisposable { public X509Certificate2 Certificate { get; private set; } - public ServerState(string certificatePath) => Certificate = new X509Certificate2(certificatePath, "password"); + public ServerState(string certificatePath) => + Certificate = X509CertificateLoader.LoadPkcs12FromFile(certificatePath, "password"); public void Dispose() => Certificate?.Dispose(); public void reloadCertificate(string certificatePath) { Certificate?.Dispose(); - Certificate = new X509Certificate2(certificatePath, "password"); + Certificate = X509CertificateLoader.LoadPkcs12FromFile(certificatePath, "password"); } } @@ -266,10 +271,12 @@ private static void serverHotCertificateReload(TestHelper helper, string certifi Console.Out.Write("server hot certificate reload... "); Console.Out.Flush(); - using var trustedRootCertificatesCA1 = new X509Certificate2(Path.Combine(certificatesPath, "ca1/ca1_cert.pem")); - using var trustedRootCertificatesCA2 = new X509Certificate2(Path.Combine(certificatesPath, "ca2/ca2_cert.pem")); + using X509Certificate2 trustedRootCertificatesCA1 = + X509CertificateLoader.LoadCertificateFromFile(Path.Combine(certificatesPath, "ca1", "ca1_cert.pem")); + using X509Certificate2 trustedRootCertificatesCA2 = + X509CertificateLoader.LoadCertificateFromFile(Path.Combine(certificatesPath, "ca2", "ca2_cert.pem")); - using var serverState = new ServerState(Path.Combine(certificatesPath, "ca1/server.p12")); + using var serverState = new ServerState(Path.Combine(certificatesPath, "ca1", "server.p12")); var serverOptions = new SslServerAuthenticationOptions { @@ -310,7 +317,7 @@ private static void serverHotCertificateReload(TestHelper helper, string certifi } } - serverState.reloadCertificate(Path.Combine(certificatesPath, "ca2/server.p12")); + serverState.reloadCertificate(Path.Combine(certificatesPath, "ca2", "server.p12")); { // CA2 is accepted with the new configuration var clientOptions = new SslClientAuthenticationOptions diff --git a/csharp/test/IceSSL/configuration/msbuild/client/client.csproj b/csharp/test/IceSSL/configuration/msbuild/client/client.csproj index b54d1b85bdd..cae436dcaf6 100644 --- a/csharp/test/IceSSL/configuration/msbuild/client/client.csproj +++ b/csharp/test/IceSSL/configuration/msbuild/client/client.csproj @@ -26,4 +26,8 @@ + + + + diff --git a/scripts/Util.py b/scripts/Util.py index b9d81d669b6..de65b19914c 100644 --- a/scripts/Util.py +++ b/scripts/Util.py @@ -3662,14 +3662,17 @@ class CSharpMapping(Mapping): class Config(Mapping.Config): @classmethod def getSupportedArgs(self): - return ("", ["csharp-config=", "coverage-session="]) + return ("", ["csharp-config=", "coverage-session=", "target-framework="]) @classmethod def usage(self): print("") print("C# mapping options:") - print("--csharp-config= C# build configuration for .NET executables (overrides --config).") - print("--coverage-session= Run tests the dotnet-coverage using the given session ID.") + print("--csharp-config= C# build configuration for .NET executables (overrides --config).") + print("--coverage-session= Run tests the dotnet-coverage using the given session ID.") + print("--target-framework= Choose the target framework used to run .NET tests") + print(" It should match the target framework used to build the tests") + print(" (default: net8.0) supported values: (net8.0|net9.0|net10.0)") def __init__(self, options=[]): Mapping.Config.__init__(self, options) @@ -3679,6 +3682,7 @@ def __init__(self, options=[]): self.buildConfig = "Release" if os.environ.get("OPTIMIZE", "yes") != "no" else "Debug" self.dotnetCoverageSession = "" + self.targetFramework = "net8.0" parseOptions( self, @@ -3686,11 +3690,12 @@ def __init__(self, options=[]): { "csharp-config": "buildConfig", "coverage-session": "dotnetCoverageSession", + "target-framework": "targetFramework", }, ) def getTargetFramework(self, current): - return "net8.0" + return current.config.targetFramework def getBuildDir(self, name, current): return os.path.join("msbuild", name, self.getTargetFramework(current))